From e81861a0cda428f6e5b4e29e7d9fb8912b12b8e4 Mon Sep 17 00:00:00 2001 From: fros4943 Date: Mon, 21 Aug 2006 12:11:16 +0000 Subject: [PATCH] added cooja; 'The Contiki OS Java Simulator' --- platform/cooja/Makefile.cooja | 122 + platform/cooja/contiki-conf.h | 296 +++ platform/cooja/cooyah.c | 89 + platform/cooja/dev/beep.c | 107 + platform/cooja/dev/beep.h | 61 + platform/cooja/dev/button-sensor.c | 115 + platform/cooja/dev/button-sensor.h | 39 + platform/cooja/dev/ip.c | 67 + platform/cooja/dev/ip.h | 35 + platform/cooja/dev/irq.c | 36 + platform/cooja/dev/leds-arch.c | 68 + platform/cooja/dev/moteid.c | 60 + platform/cooja/dev/moteid.h | 37 + platform/cooja/dev/pir-sensor.c | 117 + platform/cooja/dev/pir-sensor.h | 39 + platform/cooja/dev/radio-arch.c | 205 ++ platform/cooja/dev/radio-arch.h | 41 + platform/cooja/dev/rs232.c | 109 + platform/cooja/dev/rs232.h | 118 + platform/cooja/dev/vib-sensor.c | 117 + platform/cooja/dev/vib-sensor.h | 39 + platform/cooja/lib/simEnvChange.c | 58 + platform/cooja/lib/simEnvChange.h | 62 + platform/cooja/node-id.h | 49 + platform/cooja/sys/clock.c | 70 + platform/cooja/sys/log.c | 74 + platform/cooja/testbutton.c | 72 + platform/cooja/testbutton.h | 39 + platform/cooja/testetimer.c | 78 + platform/cooja/testetimer.h | 39 + platform/cooja/testserial.c | 81 + platform/cooja/testserial.h | 39 + tools/cooja/build.xml | 88 + tools/cooja/config/code_main_template | 219 ++ tools/cooja/config/cooja_default.config | 34 + .../cooja/config/external_tools_linux.config | 17 + .../cooja/config/external_tools_win32.config | 17 + tools/cooja/config/log4j_config.xml | 25 + tools/cooja/examples/jni_test/build.xml | 153 ++ .../examples/jni_test/level1/Level1.java | 53 + tools/cooja/examples/jni_test/level1/level1.c | 40 + .../examples/jni_test/level2/Level2.java | 53 + tools/cooja/examples/jni_test/level2/level2.c | 40 + .../examples/jni_test/level3/Level3.java | 152 ++ tools/cooja/examples/jni_test/level3/level3.c | 43 + .../examples/jni_test/level4/Level4.java | 177 ++ tools/cooja/examples/jni_test/level4/level4.c | 50 + .../examples/jni_test/level5/Level5.java | 205 ++ tools/cooja/examples/jni_test/level5/level5.c | 68 + .../examples/userplatform_debug/cooja.config | 1 + .../userplatform_debug/java/MoteDebugger.java | 170 ++ .../examples/userplatform_new_apps/app1.c | 61 + .../examples/userplatform_new_apps/app2.c | 61 + .../userplatform_new_apps/cooja.config | 1 + .../userplatform_new_interface/cooja.config | 2 + .../userplatform_new_interface/dummy_intf.c | 58 + .../userplatform_new_interface/dummy_intf.h | 40 + .../java/DummyInterface.java | 94 + .../userplatform_new_plugin/cooja.config | 1 + .../java/MyDummyPlugin.java | 93 + .../userplatform_new_radiomedium/cooja.config | 1 + .../java/DummyRadioMedium.java | 94 + .../examples/userplatform_uaodv/cooja.config | 3 + .../userplatform_uaodv/java/UAODVControl.java | 158 ++ .../userplatform_uaodv/java/VisUAODV.java | 108 + .../userplatform_uaodv/uaodv-example.c | 77 + .../java/se/sics/cooja/ClassDescription.java | 49 + .../java/se/sics/cooja/ConnectionLogger.java | 176 ++ tools/cooja/java/se/sics/cooja/CoreComm.java | 225 ++ .../se/sics/cooja/DirectoryClassLoader.java | 143 ++ tools/cooja/java/se/sics/cooja/GUI.java | 1612 +++++++++++++ .../java/se/sics/cooja/IPDistributor.java | 75 + tools/cooja/java/se/sics/cooja/Mote.java | 208 ++ .../java/se/sics/cooja/MoteInterface.java | 158 ++ .../se/sics/cooja/MoteInterfaceHandler.java | 358 +++ .../cooja/java/se/sics/cooja/MoteMemory.java | 79 + tools/cooja/java/se/sics/cooja/MoteType.java | 149 ++ .../se/sics/cooja/PassiveMoteInterface.java | 58 + .../java/se/sics/cooja/PlatformConfig.java | 359 +++ .../cooja/java/se/sics/cooja/Positioner.java | 93 + .../java/se/sics/cooja/RadioConnection.java | 150 ++ .../cooja/java/se/sics/cooja/RadioMedium.java | 176 ++ .../java/se/sics/cooja/SectionMoteMemory.java | 512 ++++ .../cooja/java/se/sics/cooja/Simulation.java | 735 ++++++ tools/cooja/java/se/sics/cooja/VisPlugin.java | 95 + .../java/se/sics/cooja/VisPluginType.java | 109 + .../sics/cooja/contikimote/ContikiMote.java | 317 +++ .../contikimote/ContikiMoteInterface.java | 48 + .../cooja/contikimote/ContikiMoteType.java | 898 +++++++ .../contikimote/ContikiMoteTypeDialog.java | 2106 +++++++++++++++++ .../contikimote/interfaces/ContikiBeeper.java | 136 ++ .../contikimote/interfaces/ContikiButton.java | 176 ++ .../contikimote/interfaces/ContikiClock.java | 117 + .../interfaces/ContikiIPAddress.java | 187 ++ .../contikimote/interfaces/ContikiLED.java | 260 ++ .../contikimote/interfaces/ContikiLog.java | 169 ++ .../contikimote/interfaces/ContikiMoteID.java | 167 ++ .../contikimote/interfaces/ContikiPIR.java | 159 ++ .../contikimote/interfaces/ContikiRS232.java | 256 ++ .../contikimote/interfaces/ContikiRadio.java | 305 +++ .../contikimote/interfaces/ContikiVib.java | 159 ++ .../java/se/sics/cooja/corecomm/Lib1.java | 60 + .../java/se/sics/cooja/corecomm/Lib2.java | 60 + .../java/se/sics/cooja/corecomm/Lib3.java | 60 + .../java/se/sics/cooja/corecomm/Lib4.java | 60 + .../java/se/sics/cooja/corecomm/Lib5.java | 60 + .../java/se/sics/cooja/corecomm/Lib6.java | 60 + .../java/se/sics/cooja/corecomm/Lib7.java | 60 + .../java/se/sics/cooja/corecomm/Lib8.java | 60 + .../se/sics/cooja/dialogs/AddMoteDialog.java | 502 ++++ .../sics/cooja/dialogs/CreateSimDialog.java | 339 +++ .../cooja/dialogs/ExternalToolsDialog.java | 180 ++ .../se/sics/cooja/dialogs/MessageList.java | 152 ++ .../cooja/dialogs/UserPlatformsDialog.java | 428 ++++ .../se/sics/cooja/interfaces/Battery.java | 287 +++ .../java/se/sics/cooja/interfaces/Beeper.java | 50 + .../java/se/sics/cooja/interfaces/Button.java | 66 + .../java/se/sics/cooja/interfaces/Clock.java | 60 + .../se/sics/cooja/interfaces/IPAddress.java | 66 + .../java/se/sics/cooja/interfaces/LED.java | 65 + .../java/se/sics/cooja/interfaces/Log.java | 50 + .../java/se/sics/cooja/interfaces/MoteID.java | 56 + .../java/se/sics/cooja/interfaces/PIR.java | 50 + .../se/sics/cooja/interfaces/Position.java | 234 ++ .../java/se/sics/cooja/interfaces/Radio.java | 133 ++ .../cooja/ipdistributors/IdIPDistributor.java | 77 + .../ipdistributors/RandomIPDistributor.java | 57 + .../ipdistributors/SpatialIPDistributor.java | 106 + .../java/se/sics/cooja/motes/DummyMote.java | 222 ++ .../se/sics/cooja/motes/DummyMoteType.java | 158 ++ .../se/sics/cooja/plugins/LogListener.java | 122 + .../sics/cooja/plugins/MoteInformation.java | 234 ++ .../cooja/plugins/MoteInterfaceViewer.java | 152 ++ .../cooja/plugins/MoteTypeInformation.java | 123 + .../se/sics/cooja/plugins/SimControl.java | 262 ++ .../se/sics/cooja/plugins/SimInformation.java | 231 ++ .../sics/cooja/plugins/VariableWatcher.java | 310 +++ .../se/sics/cooja/plugins/VisBattery.java | 138 ++ .../java/se/sics/cooja/plugins/VisState.java | 151 ++ .../se/sics/cooja/plugins/VisTraffic.java | 184 ++ .../se/sics/cooja/plugins/Visualizer2D.java | 539 +++++ .../cooja/positioners/EllipsePositioner.java | 78 + .../cooja/positioners/LinearPositioner.java | 148 ++ .../cooja/positioners/RandomPositioner.java | 74 + .../cooja/radiomediums/SilentRadioMedium.java | 89 + .../radiomediums/StandardRadioMedium.java | 495 ++++ tools/cooja/lib/jdom.jar | Bin 0 -> 149522 bytes tools/cooja/lib/log4j.jar | Bin 0 -> 350677 bytes 148 files changed, 23162 insertions(+) create mode 100644 platform/cooja/Makefile.cooja create mode 100644 platform/cooja/contiki-conf.h create mode 100644 platform/cooja/cooyah.c create mode 100644 platform/cooja/dev/beep.c create mode 100644 platform/cooja/dev/beep.h create mode 100644 platform/cooja/dev/button-sensor.c create mode 100644 platform/cooja/dev/button-sensor.h create mode 100644 platform/cooja/dev/ip.c create mode 100644 platform/cooja/dev/ip.h create mode 100644 platform/cooja/dev/irq.c create mode 100644 platform/cooja/dev/leds-arch.c create mode 100644 platform/cooja/dev/moteid.c create mode 100644 platform/cooja/dev/moteid.h create mode 100644 platform/cooja/dev/pir-sensor.c create mode 100644 platform/cooja/dev/pir-sensor.h create mode 100644 platform/cooja/dev/radio-arch.c create mode 100644 platform/cooja/dev/radio-arch.h create mode 100644 platform/cooja/dev/rs232.c create mode 100644 platform/cooja/dev/rs232.h create mode 100644 platform/cooja/dev/vib-sensor.c create mode 100644 platform/cooja/dev/vib-sensor.h create mode 100644 platform/cooja/lib/simEnvChange.c create mode 100644 platform/cooja/lib/simEnvChange.h create mode 100644 platform/cooja/node-id.h create mode 100644 platform/cooja/sys/clock.c create mode 100644 platform/cooja/sys/log.c create mode 100644 platform/cooja/testbutton.c create mode 100644 platform/cooja/testbutton.h create mode 100644 platform/cooja/testetimer.c create mode 100644 platform/cooja/testetimer.h create mode 100644 platform/cooja/testserial.c create mode 100644 platform/cooja/testserial.h create mode 100644 tools/cooja/build.xml create mode 100644 tools/cooja/config/code_main_template create mode 100644 tools/cooja/config/cooja_default.config create mode 100644 tools/cooja/config/external_tools_linux.config create mode 100644 tools/cooja/config/external_tools_win32.config create mode 100644 tools/cooja/config/log4j_config.xml create mode 100644 tools/cooja/examples/jni_test/build.xml create mode 100644 tools/cooja/examples/jni_test/level1/Level1.java create mode 100644 tools/cooja/examples/jni_test/level1/level1.c create mode 100644 tools/cooja/examples/jni_test/level2/Level2.java create mode 100644 tools/cooja/examples/jni_test/level2/level2.c create mode 100644 tools/cooja/examples/jni_test/level3/Level3.java create mode 100644 tools/cooja/examples/jni_test/level3/level3.c create mode 100644 tools/cooja/examples/jni_test/level4/Level4.java create mode 100644 tools/cooja/examples/jni_test/level4/level4.c create mode 100644 tools/cooja/examples/jni_test/level5/Level5.java create mode 100644 tools/cooja/examples/jni_test/level5/level5.c create mode 100644 tools/cooja/examples/userplatform_debug/cooja.config create mode 100644 tools/cooja/examples/userplatform_debug/java/MoteDebugger.java create mode 100644 tools/cooja/examples/userplatform_new_apps/app1.c create mode 100644 tools/cooja/examples/userplatform_new_apps/app2.c create mode 100644 tools/cooja/examples/userplatform_new_apps/cooja.config create mode 100644 tools/cooja/examples/userplatform_new_interface/cooja.config create mode 100644 tools/cooja/examples/userplatform_new_interface/dummy_intf.c create mode 100644 tools/cooja/examples/userplatform_new_interface/dummy_intf.h create mode 100644 tools/cooja/examples/userplatform_new_interface/java/DummyInterface.java create mode 100644 tools/cooja/examples/userplatform_new_plugin/cooja.config create mode 100644 tools/cooja/examples/userplatform_new_plugin/java/MyDummyPlugin.java create mode 100644 tools/cooja/examples/userplatform_new_radiomedium/cooja.config create mode 100644 tools/cooja/examples/userplatform_new_radiomedium/java/DummyRadioMedium.java create mode 100644 tools/cooja/examples/userplatform_uaodv/cooja.config create mode 100644 tools/cooja/examples/userplatform_uaodv/java/UAODVControl.java create mode 100644 tools/cooja/examples/userplatform_uaodv/java/VisUAODV.java create mode 100644 tools/cooja/examples/userplatform_uaodv/uaodv-example.c create mode 100644 tools/cooja/java/se/sics/cooja/ClassDescription.java create mode 100644 tools/cooja/java/se/sics/cooja/ConnectionLogger.java create mode 100644 tools/cooja/java/se/sics/cooja/CoreComm.java create mode 100644 tools/cooja/java/se/sics/cooja/DirectoryClassLoader.java create mode 100644 tools/cooja/java/se/sics/cooja/GUI.java create mode 100644 tools/cooja/java/se/sics/cooja/IPDistributor.java create mode 100644 tools/cooja/java/se/sics/cooja/Mote.java create mode 100644 tools/cooja/java/se/sics/cooja/MoteInterface.java create mode 100644 tools/cooja/java/se/sics/cooja/MoteInterfaceHandler.java create mode 100644 tools/cooja/java/se/sics/cooja/MoteMemory.java create mode 100644 tools/cooja/java/se/sics/cooja/MoteType.java create mode 100644 tools/cooja/java/se/sics/cooja/PassiveMoteInterface.java create mode 100644 tools/cooja/java/se/sics/cooja/PlatformConfig.java create mode 100644 tools/cooja/java/se/sics/cooja/Positioner.java create mode 100644 tools/cooja/java/se/sics/cooja/RadioConnection.java create mode 100644 tools/cooja/java/se/sics/cooja/RadioMedium.java create mode 100644 tools/cooja/java/se/sics/cooja/SectionMoteMemory.java create mode 100644 tools/cooja/java/se/sics/cooja/Simulation.java create mode 100644 tools/cooja/java/se/sics/cooja/VisPlugin.java create mode 100644 tools/cooja/java/se/sics/cooja/VisPluginType.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/ContikiMote.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteInterface.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteType.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteTypeDialog.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiBeeper.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiButton.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiClock.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiIPAddress.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLED.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLog.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiMoteID.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiPIR.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRS232.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRadio.java create mode 100644 tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiVib.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib1.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib2.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib3.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib4.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib5.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib6.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib7.java create mode 100644 tools/cooja/java/se/sics/cooja/corecomm/Lib8.java create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/AddMoteDialog.java create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/CreateSimDialog.java create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/ExternalToolsDialog.java create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/MessageList.java create mode 100644 tools/cooja/java/se/sics/cooja/dialogs/UserPlatformsDialog.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Battery.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Beeper.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Button.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Clock.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/IPAddress.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/LED.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Log.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/MoteID.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/PIR.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Position.java create mode 100644 tools/cooja/java/se/sics/cooja/interfaces/Radio.java create mode 100644 tools/cooja/java/se/sics/cooja/ipdistributors/IdIPDistributor.java create mode 100644 tools/cooja/java/se/sics/cooja/ipdistributors/RandomIPDistributor.java create mode 100644 tools/cooja/java/se/sics/cooja/ipdistributors/SpatialIPDistributor.java create mode 100644 tools/cooja/java/se/sics/cooja/motes/DummyMote.java create mode 100644 tools/cooja/java/se/sics/cooja/motes/DummyMoteType.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/LogListener.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/MoteInformation.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/MoteInterfaceViewer.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/MoteTypeInformation.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/SimControl.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/SimInformation.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/VariableWatcher.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/VisBattery.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/VisState.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/VisTraffic.java create mode 100644 tools/cooja/java/se/sics/cooja/plugins/Visualizer2D.java create mode 100644 tools/cooja/java/se/sics/cooja/positioners/EllipsePositioner.java create mode 100644 tools/cooja/java/se/sics/cooja/positioners/LinearPositioner.java create mode 100644 tools/cooja/java/se/sics/cooja/positioners/RandomPositioner.java create mode 100644 tools/cooja/java/se/sics/cooja/radiomediums/SilentRadioMedium.java create mode 100644 tools/cooja/java/se/sics/cooja/radiomediums/StandardRadioMedium.java create mode 100644 tools/cooja/lib/jdom.jar create mode 100644 tools/cooja/lib/log4j.jar diff --git a/platform/cooja/Makefile.cooja b/platform/cooja/Makefile.cooja new file mode 100644 index 000000000..cda810837 --- /dev/null +++ b/platform/cooja/Makefile.cooja @@ -0,0 +1,122 @@ +## The COOJA Simulator - cooja platform makefile +## +## This makefile's main purpose is to compile source files +## generated by the COOJA Simulator and should +## only be called from inside the simulator... +## +## However, it can also be used as a shortcut to startup the +## simulator from a customized user platform. +## See the user platform examples. + +########################################################### +ifndef COMPILE_MAIN # << shortcut, startup simulator >> + +# The COOJA Simulator jar-file location (default) +ifndef COOJA_JAR + COOJA_JAR=$(CONTIKI)/tools/cooja/dist/cooja.jar +endif + +# Java binary (default) +ifndef JAVA + JAVA=java +endif + +ifndef WINDIR + ifdef OS + ifneq (,$(findstring Windows,$(OS))) + WINDIR := Windows + endif + endif +endif + +ifndef WINDIR + # This settings are for UNIX + SEPARATOR=: +else + # These setting are for MS-DOS/Windows 95/Windows NT + SEPARATOR=; +endif + +# Command to start simulator with a source file argument +COMMAND = $(JAVA) -DPATH_CONTIKI=$(CONTIKI) -DQUICKSTART_APP=$@ \ + -cp "$(COOJA_JAR)$(SEPARATOR)$(COOJA_JAVA)" se.sics.cooja.GUI $(CLASS_CONFIGS) + +# Delete pre-defined project sourcefiles (messes up make execution) +PROJECT_SOURCEFILES = # OBS! Ugly fix + +%: + @echo "Starting simulator (using shortcut)" + $(COMMAND) + +########################################################### +else # << compile generated source file >> + +### Check/create COOJA parameters (output files) + +ifndef CONTIKI + $(error CONTIKI not defined!) +endif + +ifndef TYPEID + $(error TYPEID not defined!) +endif + +OUTPUT_DIR = obj_cooja +LIBFILE = $(OUTPUT_DIR)/$(TYPEID).library +DEPFILE = $(OUTPUT_DIR)/$(TYPEID).a +MAPFILE = $(OUTPUT_DIR)/$(TYPEID).map +MAINFILE = $(OUTPUT_DIR)/$(TYPEID).co + +COOJA = $(CONTIKI)/platform/$(TARGET) +CONTIKI_TARGET_DIRS = . dev lib sys + +COOJA_APPS = testbutton.c testetimer.c testserial.c cooyah.c + +COOJA_DEV = $(patsubst $(COOJA)/dev/%.c,%.c,$(wildcard $(COOJA)/dev/*.c)) + +COOJA_LIB = $(patsubst $(COOJA)/lib/%.c,%.c,$(wildcard $(COOJA)/lib/*.c)) + +COOJA_SYS = $(patsubst $(COOJA)/sys/%.c,%.c,$(wildcard $(COOJA)/sys/*.c)) + +CORE_FILES = random.c sensors.c leds.c serial.c + +CONTIKI_TARGET_SOURCEFILES = \ +$(COOJA_APPS) $(COOJA_DEV) $(COOJA_LIB) $(COOJA_SYS) $(CORE_FILES) + +CONTIKI_SOURCEFILES += $(CONTIKI_TARGET_SOURCEFILES) + +.SUFFIXES: + +### Define the CPU directory +CONTIKI_CPU=$(CONTIKI)/cpu/x86 + +### Compiler definitions (search contiki core last) +CC = gcc +LD = ld +AS = as +OBJCOPY = objcopy +STRIP = strip +CFLAGSNO = -I. -I$(CONTIKI_CPU) \ + $(EXTRA_CC_ARGS) \ + -I$(COOJA) \ + -I$(CONTIKI)/core \ + -DWITH_UIP -DWITH_ASCII \ + -Wall -g -I. -I/usr/local/include +CFLAGS = $(CFLAGSNO) + +### Setup directory search path for source files + +CONTIKI_TARGET_DIRS_CONCAT = ${addprefix $(COOJA)/, \ + $(CONTIKI_TARGET_DIRS)} + +vpath %.c $(PROJECTDIRS) \ + $(CONTIKIDIRS) $(APPDIRS) $(CONTIKI_TARGET_DIRS_CONCAT) \ + $(CONTIKI_CPU) + +$(LIBFILE): $(MAINFILE) $(PROJECT_OBJECTFILES) $(DEPFILE) + $(LD) -Map=$(MAPFILE) -shared $(LD_ARGS_1) -o $@ $^ $(LD_ARGS_2) + +$(DEPFILE): ${addprefix $(OBJECTDIR)/, $(CONTIKI_SOURCEFILES:.c=.o)} + $(AR) rcf $@ $^ + +endif ## END OF 'COMPILE GENERATED CONTIKI MAIN SOURCE FILE' diff --git a/platform/cooja/contiki-conf.h b/platform/cooja/contiki-conf.h new file mode 100644 index 000000000..437ede89b --- /dev/null +++ b/platform/cooja/contiki-conf.h @@ -0,0 +1,296 @@ +#ifndef __CONTIKI_CONF_H__ +#define __CONTIKI_CONF_H__ + +/* + * Copyright (c) 2005, Adam Dunkels. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * This file is part of the Contiki desktop OS + * + * $Id: contiki-conf.h,v 1.1 2006/08/21 12:11:17 fros4943 Exp $ + * + */ + +#define CC_CONF_REGISTER_ARGS 1 +#define CC_CONF_FUNCTION_POINTER_ARGS 1 +#define CC_CONF_FASTCALL + +#define CC_CONF_VA_ARGS 1 + +/*------------------------------------------------------------------------------*/ +/** + * \defgroup uipopttypedef uIP type definitions + * @{ + */ + +#include + +/** + * The 8-bit unsigned data type. + * + * This may have to be tweaked for your particular compiler. "unsigned + * char" works for most compilers. + */ +typedef uint8_t u8_t; + +/** + * The 16-bit unsigned data type. + * + * This may have to be tweaked for your particular compiler. "unsigned + * short" works for most compilers. + */ +typedef uint16_t u16_t; + +/** + * The 32-bit unsigned data type. + * + * This may have to be tweaked for your particular compiler. "unsigned + * short" works for most compilers. + */ +typedef uint32_t u32_t; + +/** + * The statistics data type. + * + * This datatype determines how high the statistics counters are able + * to count. + */ +typedef unsigned short uip_stats_t; + +/** @} */ + + +/*------------------------------------------------------------------------------*/ + +typedef unsigned long clock_time_t; +#define CLOCK_CONF_SECOND 1000 + + +/*------------------------------------------------------------------------------*/ + +#include "ctk/ctk-vncarch.h" + +/* + * This file is used for setting various compile time settings for the + * CTK GUI toolkit. +*/ + +/* Toggles mouse support (must have support functions in the +architecture specific files to work). */ +#define CTK_CONF_MOUSE_SUPPORT 1 + +/* Defines which key that is to be used for activating the menus */ +#define CTK_CONF_MENU_KEY CH_F1 + +/* Defines which key that is to be used for switching the frontmost + window. */ +#define CTK_CONF_WINDOWSWITCH_KEY CH_F3 + +/* Defines which key that is to be used for switching to the next + widget. */ +#define CTK_CONF_WIDGETDOWN_KEY CH_TAB + +/* Defines which key that is to be used for switching to the prevoius + widget. */ +#define CTK_CONF_WIDGETUP_KEY CH_F5 + +/* Toggles support for icons. */ +#define CTK_CONF_ICONS 1 /* 107 bytes */ + +/* Toggles support for icon bitmaps. */ +#define CTK_CONF_ICON_BITMAPS 1 + +/* Toggles support for icon textmaps. */ +#define CTK_CONF_ICON_TEXTMAPS 1 + +/* Toggles support for movable windows. */ +#define CTK_CONF_WINDOWMOVE 1 /* 333 bytes */ + +/* Toggles support for closable windows. */ +#define CTK_CONF_WINDOWCLOSE 1 /* 14 bytes */ + +/* Toggles support for menus. */ +#define CTK_CONF_MENUS 1 /* 1384 bytes */ + +/* Defines the default width of a menu. */ +#define CTK_CONF_MENUWIDTH 16 +/* The maximum number of menu items in each menu. */ +#define CTK_CONF_MAXMENUITEMS 10 + +#define CTK_CONF_WIDGET_FLAGS 1 + + +/*------------------------------------------------------------------------------*/ + +#define COLOR_BLACK 0 +#define COLOR_WHITE 1 + +#define BORDERCOLOR COLOR_BLACK +#define SCREENCOLOR COLOR_BLACK + +#define WINDOWCOLOR_FOCUS COLOR_WHITE +#define WINDOWCOLOR COLOR_WHITE + +#define DIALOGCOLOR COLOR_WHITE + +#define WIDGETCOLOR_HLINK COLOR_WHITE +#define WIDGETCOLOR_FWIN COLOR_WHITE +#define WIDGETCOLOR COLOR_WHITE +#define WIDGETCOLOR_DIALOG COLOR_WHITE +#define WIDGETCOLOR_FOCUS COLOR_WHITE + +#define MENUCOLOR COLOR_WHITE +#define OPENMENUCOLOR COLOR_WHITE +#define ACTIVEMENUITEMCOLOR COLOR_WHITE + + +/*------------------------------------------------------------------------------*/ + +/* Maximum number of clients to the telnet server */ +#define CTK_TERM_CONF_MAX_TELNET_CLIENTS 3 + +/* Telnet server port */ +#define CTK_TERM_CONF_TELNET_PORT 23 + +/* Serial server output buffer size */ +#define CTK_TERM_CONF_SERIAL_BUFFER_SIZE 300 + +/* Maximum number of clients to the terminal module. + Should be set to CTK_TERM_CONF_MAX_TELNET_CLIENTS or + CTK_TERM_CONF_MAX_TELNET_CLIENTS+1 if the serial server is used too +*/ +#define CTK_TERM_CONF_MAX_CLIENTS (CTK_TERM_CONF_MAX_TELNET_CLIENTS+1) + + +/*------------------------------------------------------------------------------*/ + +#define CTK_VNCSERVER_CONF_NUMCONNS 10 + +#define CTK_VNCSERVER_CONF_MAX_ICONS 16 + + +/*------------------------------------------------------------------------------*/ + +#define EMAIL_CONF_WIDTH 36 +#define EMAIL_CONF_HEIGHT 16 + + +/*------------------------------------------------------------------------------*/ + +#define IRC_CONF_WIDTH 78 +#define IRC_CONF_HEIGHT 21 + +#define IRC_CONF_SYSTEM_STRING "GTK simulation" + + +/*------------------------------------------------------------------------------*/ + +#define LIBCONIO_CONF_SCREEN_WIDTH 80 +#define LIBCONIO_CONF_SCREEN_HEIGHT 45 + + +/*------------------------------------------------------------------------------*/ + +#define LOG_CONF_ENABLED 1 + + +/*------------------------------------------------------------------------------*/ + +#define PROGRAM_HANDLER_CONF_MAX_NUMDSCS 10 + + +/*------------------------------------------------------------------------------*/ + +#define SHELL_GUI_CONF_XSIZE 46 +#define SHELL_GUI_CONF_YSIZE 22 + + +/*------------------------------------------------------------------------------*/ + +#define TELNETD_CONF_LINELEN 80 +#define TELNETD_CONF_NUMLINES 16 + + +/*------------------------------------------------------------------------------*/ + +#define UIP_CONF_MAX_CONNECTIONS 40 +#define UIP_CONF_MAX_LISTENPORTS 40 +#define UIP_CONF_BUFFER_SIZE 420 + +#define UIP_CONF_BYTE_ORDER LITTLE_ENDIAN + +#define UIP_CONF_BROADCAST 1 + +#define UIP_CONF_TCP_SPLIT 1 + +#define UIP_CONF_LOGGING 0 + +#define UIP_CONF_UDP_CHECKSUMS 0 + + +/*------------------------------------------------------------------------------*/ + +#define VNC_CONF_VIEWPORT_WIDTH (32*8) +#define VNC_CONF_VIEWPORT_HEIGHT (16*8) + +#define VNC_CONF_REFRESH_ROWS 8 + + +/*------------------------------------------------------------------------------*/ + +/* The size of the HTML viewing area. */ +#define WWW_CONF_WEBPAGE_WIDTH 76 +#define WWW_CONF_WEBPAGE_HEIGHT 30 + +/* The size of the "Back" history. */ +#define WWW_CONF_HISTORY_SIZE 40 + +/* Defines the maximum length of an URL */ +#define WWW_CONF_MAX_URLLEN 200 + +/* The maxiumum number of widgets (i.e., hyperlinks, form elements) on + a page. */ +#define WWW_CONF_MAX_NUMPAGEWIDGETS 80 + +/* Turns
support on or off; must be on for forms to work. */ +#define WWW_CONF_RENDERSTATE 1 + +/* Toggles support for HTML forms. */ +#define WWW_CONF_FORMS 1 + +/* Maximum lengths for various HTML form parameters. */ +#define WWW_CONF_MAX_FORMACTIONLEN 200 +#define WWW_CONF_MAX_FORMNAMELEN 200 +#define WWW_CONF_MAX_INPUTNAMELEN 200 +#define WWW_CONF_MAX_INPUTVALUELEN 240 + +#define WWW_CONF_PAGEVIEW 1 + +#define LOADER_CONF_ARCH "loader/dlloader.h" + +#endif /* __CONTIKI_CONF_H__ */ diff --git a/platform/cooja/cooyah.c b/platform/cooja/cooyah.c new file mode 100644 index 000000000..508f8fe18 --- /dev/null +++ b/platform/cooja/cooyah.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: cooyah.c,v 1.1 2006/08/21 12:11:16 fros4943 Exp $ + */ + +#include +#include +#include "net/uip.h" +#include "lib/sensors.h" +#include "dev/leds.h" +#include "dev/button-sensor.h" +#include "sys/log.h" +#include "node-id.h" + +#define COOYAH_PORT 1234 + +PROCESS(cooyah_example_process, "Example process for report"); +AUTOSTART_PROCESSES(&cooyah_example_process); + +static struct uip_udp_conn *broadcast_conn; +/*---------------------------------------------------------------------*/ +PROCESS_THREAD(cooyah_example_process, ev, data) +{ + PROCESS_BEGIN(); + + log_message("Example process started", ""); + + broadcast_conn = udp_broadcast_new(COOYAH_PORT , NULL); + + button_sensor.activate(); + + while(1) { + PROCESS_WAIT_EVENT(); + log_message("An event occured: ", ""); + + if(ev == PROCESS_EVENT_EXIT) { + log_message("shutting down\n", ""); + break; + } + + if(ev == sensors_event && data == &button_sensor && button_sensor.value(0)) { + log_message("the button is pressed, sending packet\n", ""); + + tcpip_poll_udp(broadcast_conn); + PROCESS_WAIT_UNTIL(ev == tcpip_event && uip_poll()); + uip_send("cooyah COOJA", 12); + } + + if(ev == sensors_event && data == &button_sensor && !button_sensor.value(0)) { + log_message("the button was released again, doing nothing\n", ""); + } + + if(ev == tcpip_event && uip_newdata()) { + log_message("a packet was received, turning on leds\n", ""); + log_message("PACKET DATA: ", uip_appdata); + leds_on(LEDS_ALL); + } + + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------*/ diff --git a/platform/cooja/dev/beep.c b/platform/cooja/dev/beep.c new file mode 100644 index 000000000..2ef56b42b --- /dev/null +++ b/platform/cooja/dev/beep.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: beep.c,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#include "dev/beep.h" +#include "lib/simEnvChange.h" + +const struct simInterface beep_interface; + +// COOJA variables +char simBeeped; + +/*-----------------------------------------------------------------------------------*/ +void +beep_alarm(int alarmmode, int len) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_beep(int i) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep(void) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_down(int d) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_on(void) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_off(void) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_spinup(void) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +beep_quick(int n) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +void beep_long(clock_time_t len) +{ + simBeeped = 1; +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(beep_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/beep.h b/platform/cooja/dev/beep.h new file mode 100644 index 000000000..fbe6bcab8 --- /dev/null +++ b/platform/cooja/dev/beep.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: beep.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#ifndef __BEEP_H__ +#define __BEEP_H__ + +#include "sys/clock.h" + +#define BEEP_ON 1 +#define BEEP_OFF 0 + +#define BEEP_ALARM1 1 +#define BEEP_ALARM2 2 + +void beep_beep(int len); + +void beep_alarm(int alarmmode, int len); + +void beep(void); + +void beep_down(int len); + +void beep_on(void); + +void beep_off(void); + +void beep_spinup(void); + +void beep_long(clock_time_t len); + +void beep_quick(int num); + +#endif /* __BEEP_H__ */ diff --git a/platform/cooja/dev/button-sensor.c b/platform/cooja/dev/button-sensor.c new file mode 100644 index 000000000..e487bc99b --- /dev/null +++ b/platform/cooja/dev/button-sensor.c @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: button-sensor.c,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#include "lib/sensors.h" +#include "dev/button-sensor.h" +#include "lib/simEnvChange.h" + +const struct simInterface button_interface; +const struct sensors_sensor button_sensor; + +// COOJA variables +char simButtonChanged; +char simButtonIsDown; +char simButtonIsActive; + +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ + simButtonIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static int +irq(void) +{ + return 1; +} +/*---------------------------------------------------------------------------*/ +static void +activate(void) +{ + simButtonIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static void +deactivate(void) +{ + simButtonIsActive = 0; +} +/*---------------------------------------------------------------------------*/ +static int +active(void) +{ + return simButtonIsActive; +} +/*---------------------------------------------------------------------------*/ +static unsigned int +value(int type) +{ + return simButtonIsDown; +} +/*---------------------------------------------------------------------------*/ +static int +configure(int type, void *c) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void * +status(int type) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + // Check if button value has changed + if (simButtonChanged && simButtonIsActive) { + sensors_changed(&button_sensor); + simButtonChanged = 0; + } +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*---------------------------------------------------------------------------*/ + +SIM_INTERFACE(button_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); + +SENSORS_SENSOR(button_sensor, BUTTON_SENSOR, + init, irq, activate, deactivate, active, + value, configure, status); diff --git a/platform/cooja/dev/button-sensor.h b/platform/cooja/dev/button-sensor.h new file mode 100644 index 000000000..3d39ec59a --- /dev/null +++ b/platform/cooja/dev/button-sensor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: button-sensor.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#ifndef __BUTTON_H__ +#define __BUTTON_H__ + +extern const struct sensors_sensor button_sensor; + +#define BUTTON_SENSOR "Button" + +#endif /* __BUTTON_H__ */ diff --git a/platform/cooja/dev/ip.c b/platform/cooja/dev/ip.c new file mode 100644 index 000000000..cf5ba5401 --- /dev/null +++ b/platform/cooja/dev/ip.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ip.c,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#include "dev/ip.h" +#include "lib/simEnvChange.h" + +#include "net/uip.h" + +const struct simInterface ip_interface; + +// COOJA variables +char simIPa; +char simIPb; +char simIPc; +char simIPd; +char simIPChanged; + +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + uip_ipaddr_t hostaddr; + if (simIPChanged) { + uip_ipaddr(&hostaddr, simIPa, simIPb, simIPc, simIPd); + + uip_sethostaddr(&hostaddr); + simIPChanged = 0; + } +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(ip_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/ip.h b/platform/cooja/dev/ip.h new file mode 100644 index 000000000..f9b33ca35 --- /dev/null +++ b/platform/cooja/dev/ip.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ip.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#ifndef __IP_H__ +#define __IP_H__ + +#endif /* __IP_H__ */ diff --git a/platform/cooja/dev/irq.c b/platform/cooja/dev/irq.c new file mode 100644 index 000000000..d1e2ae169 --- /dev/null +++ b/platform/cooja/dev/irq.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: irq.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +void +irq_init(void) +{ +} + diff --git a/platform/cooja/dev/leds-arch.c b/platform/cooja/dev/leds-arch.c new file mode 100644 index 000000000..12344309c --- /dev/null +++ b/platform/cooja/dev/leds-arch.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: leds-arch.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#include "dev/leds.h" +#include "lib/simEnvChange.h" + +const struct simInterface leds_interface; + +// COOJA variables +unsigned char simLedsValue; + +/*-----------------------------------------------------------------------------------*/ +void leds_arch_init() { + simLedsValue = 0; +} +/*-----------------------------------------------------------------------------------*/ +unsigned char leds_arch_get() { + return simLedsValue; +} +/*-----------------------------------------------------------------------------------*/ +void leds_arch_set(unsigned char leds) { + if(leds != simLedsValue) { + simLedsValue = leds; + } +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(leds_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/moteid.c b/platform/cooja/dev/moteid.c new file mode 100644 index 000000000..8de4f813b --- /dev/null +++ b/platform/cooja/dev/moteid.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: moteid.c,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#include "dev/moteid.h" +#include "lib/simEnvChange.h" +#include "lib/random.h" + +const struct simInterface moteid_interface; + +// COOJA variables +int simMoteID; +char simMoteIDChanged; + +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + if (simMoteIDChanged) { + simMoteIDChanged = 0; + random_init((simMoteID+1) * 100 % 99); + } +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(moteid_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/moteid.h b/platform/cooja/dev/moteid.h new file mode 100644 index 000000000..2d9f2c6c6 --- /dev/null +++ b/platform/cooja/dev/moteid.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: moteid.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#ifndef __MOTEID_H__ +#define __MOTEID_H__ + +extern int simMoteID; + +#endif /* __MOTEID_H__ */ diff --git a/platform/cooja/dev/pir-sensor.c b/platform/cooja/dev/pir-sensor.c new file mode 100644 index 000000000..73842a90b --- /dev/null +++ b/platform/cooja/dev/pir-sensor.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: pir-sensor.c,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#include "lib/sensors.h" +#include "dev/pir-sensor.h" +#include "lib/simEnvChange.h" + +const struct simInterface pir_interface; +const struct sensors_sensor pir_sensor; + +// COOJA variables +char simPirChanged; +char simPirIsActive; +char simPirValue = 0; + +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ + simPirIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static int +irq(void) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void +activate(void) +{ + simPirIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static void +deactivate(void) +{ + simPirIsActive = 0; +} +/*---------------------------------------------------------------------------*/ +static int +active(void) +{ + return simPirIsActive; +} +/*---------------------------------------------------------------------------*/ +static unsigned int +value(int type) +{ + return simPirValue; +} +/*---------------------------------------------------------------------------*/ +static int +configure(int type, void *c) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void * +status(int type) +{ + return NULL; +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + // Check if PIR value has changed + if (simPirIsActive && simPirChanged) { + simPirValue = !simPirValue; + + sensors_changed(&pir_sensor); + simPirChanged = 0; + } +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*---------------------------------------------------------------------------*/ + +SIM_INTERFACE(pir_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); + +SENSORS_SENSOR(pir_sensor, PIR_SENSOR, + init, irq, activate, deactivate, active, + value, configure, status); diff --git a/platform/cooja/dev/pir-sensor.h b/platform/cooja/dev/pir-sensor.h new file mode 100644 index 000000000..e3aa95ea4 --- /dev/null +++ b/platform/cooja/dev/pir-sensor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: pir-sensor.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#ifndef __PIR_H__ +#define __PIR_H__ + +extern const struct sensors_sensor pir_sensor; + +#define PIR_SENSOR "PIR" + +#endif /* __PIR_H__ */ diff --git a/platform/cooja/dev/radio-arch.c b/platform/cooja/dev/radio-arch.c new file mode 100644 index 000000000..b5370546c --- /dev/null +++ b/platform/cooja/dev/radio-arch.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: radio-arch.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#include "dev/radio-arch.h" +#include "dev/radio.h" + +#include "lib/simEnvChange.h" + +#include +#include +#include +#include "net/uip.h" +#include "net/uip-fw.h" +#include "sys/etimer.h" + +#include "sys/log.h" + +const struct simInterface radio_interface; + +// COOJA variables +char simReceivedPacket; +char simSentPacket; +char simReceivedPacketData[UIP_BUFSIZE]; +char simSentPacketData[UIP_BUFSIZE]; +int simSentPacketSize; +int simReceivedPacketSize; +char simEtherBusy; +int retryCounter; +char simRadioHWOn = 1; + + +// Ether process +PROCESS(ether_process, "Simulated Ether"); + +PROCESS_THREAD(ether_process, ev, data) +{ + static struct etimer send_timer; + + PROCESS_BEGIN(); + + // All outgoing messages pass through this process + // By using the COOJA variables simEtherBusy and !!!!TODO signalstrength!!!! + // this may be used to imitate a simple MAC protocol. + while(1) { + PROCESS_WAIT_EVENT(); + + // MAC protocol imitiation + // (this process is polled from simDoSend()) + + // Confirm we actually have data to send and radio hardware is on + if (simRadioHWOn && simSentPacketSize > 0) { + + // Wait some random time to avoid initial collisions + // MAC uses external random generator to get stochastic radio behaviour + etimer_set(&send_timer, rand() % 20); + PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&send_timer)); + + retryCounter = 0; + while (simEtherBusy && retryCounter < 5) { + retryCounter++; + + // Wait some random time hoping ether will free + // MAC uses external random generator to get stochastic radio behaviour + etimer_set(&send_timer, rand() % 20); + PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&send_timer)); + } + + if (simEtherBusy) { + log_message("MAC layer skipping packet", ""); + } else { + // Tell COOJA about our new packet + simSentPacket = 1; + } + } + } + + PROCESS_END(); +} + + +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + // Handle incoming network packets if any + if (simReceivedPacket) { + + + // If hardware is turned off, just remove packet + if (!simRadioHWOn) { + simReceivedPacket = 0; + simReceivedPacketSize = 0; + return; + } + + // Reset flag + simReceivedPacket = 0; + + if (simReceivedPacketSize == 0) { + fprintf(stderr, "simReceivedPacketSize == 0: Didn't I receive a packet?\n"); + return; + } + + // Copy incoming data to correct buffers and call handling routines + uip_len = simReceivedPacketSize; + + if(uip_len > UIP_BUFSIZE) { + fprintf(stderr, "doInterfaceActionsBeforeTick>> uip_len too large - dropping\n"); + uip_len = 0; + } else { + memcpy(&uip_buf[UIP_LLH_LEN], &simReceivedPacketData[0], simReceivedPacketSize); + simReceivedPacketSize = 0; + + // Handle new packet + tcpip_input(); + } + } +} + +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ + // Nothing to do +} +/*-----------------------------------------------------------------------------------*/ +u8_t +simDoSend(void) +{ + // If hardware is turned off, just remove packet + if (!simRadioHWOn) { + // Should we reset uip_len if radio is off? + uip_len = 0; + return UIP_FW_DROPPED; + } + + // If outgoing data, but too large, drop it + if(uip_len > UIP_BUFSIZE) { + fprintf(stderr, "simDoSend>> uip_len too large - dropping\n"); + uip_len = 0; + return UIP_FW_TOOLARGE; + } + + // If outgoing data, back it up, and wake ether process + if (uip_len > 0) { + // Backup packet data/size + memcpy(&simSentPacketData[0], &uip_buf[UIP_LLH_LEN], uip_len); + simSentPacketSize = uip_len; + + process_poll(ðer_process); + return UIP_FW_OK; + } + return UIP_FW_ZEROLEN; +} +/*-----------------------------------------------------------------------------------*/ +/** + * \brief Turn radio on. + * + * This function turns the radio hardware on. + */ +void +radio_on(void) { + simRadioHWOn = 1; +} +/*-----------------------------------------------------------------------------------*/ +/** + * \brief Turn radio off. + * + * This function turns the radio hardware off. + */ +void radio_off(void) { + simRadioHWOn = 0; +} +/*-----------------------------------------------------------------------------------*/ +SIM_INTERFACE(radio_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/radio-arch.h b/platform/cooja/dev/radio-arch.h new file mode 100644 index 000000000..2af20505a --- /dev/null +++ b/platform/cooja/dev/radio-arch.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: radio-arch.h,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#ifndef __RADIO_ARCH_H__ +#define __RADIO_ARCH_H__ + +#include "contiki.h" +#include "net/uip.h" + +PROCESS_NAME(ether_process); +u8_t simDoSend(void); + +#endif /* __RADIO_ARCH_H__ */ diff --git a/platform/cooja/dev/rs232.c b/platform/cooja/dev/rs232.c new file mode 100644 index 000000000..734b6591e --- /dev/null +++ b/platform/cooja/dev/rs232.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: rs232.c,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +#include "lib/sensors.h" +#include "dev/rs232.h" +#include "dev/serial.h" +#include "lib/simEnvChange.h" +#include +#include + +const struct simInterface rs232_interface; + +#define SERIAL_BUF_SIZE 1024 + +// COOJA variables +char simSerialReceivingData[SERIAL_BUF_SIZE]; +int simSerialReceivingLength; +char simSerialReceivingFlag; +char simSerialSendingData[SERIAL_BUF_SIZE]; +int simSerialSendingLength; +char simSerialSendingFlag; + +static int (* input_handler)(unsigned char) = NULL; + +/*-----------------------------------------------------------------------------------*/ +void rs232_init(void) { } +/*-----------------------------------------------------------------------------------*/ +void rs232_set_speed(unsigned char speed) { } +/*-----------------------------------------------------------------------------------*/ +void +rs232_set_input(int (*f)(unsigned char)) +{ + input_handler = f; +} +/*-----------------------------------------------------------------------------------*/ +void rs232_send(char c) { + simSerialSendingData[simSerialSendingLength] = c; + simSerialSendingLength += 1; + simSerialSendingFlag = 1; +} +/*-----------------------------------------------------------------------------------*/ +void +rs232_print(char *message) +{ + memcpy(&simSerialSendingData[0] + simSerialSendingLength, &message[0], strlen(message)); + simSerialSendingLength += strlen(message); + simSerialSendingFlag = 1; +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + int i; + + // Check if this mote has received data on RS232 + if (simSerialReceivingFlag && simSerialReceivingLength > 0) { + // Tell user specified poll function + if(input_handler != NULL) + for (i=0; i < simSerialReceivingLength; i++) + input_handler(simSerialReceivingData[i]); + + // Tell serial process + for (i=0; i < simSerialReceivingLength; i++) + serial_input_byte(simSerialReceivingData[i]); + + serial_input_byte(0x0a); + + simSerialReceivingLength = 0; + simSerialReceivingFlag = 0; + } +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(rs232_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/dev/rs232.h b/platform/cooja/dev/rs232.h new file mode 100644 index 000000000..166243a24 --- /dev/null +++ b/platform/cooja/dev/rs232.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Contiki operating system. + * + * @(#)$Id: rs232.h,v 1.1 2006/08/21 12:11:19 fros4943 Exp $ + */ + +/** \addtogroup esb + * @{ */ + +/** + * \defgroup esbrs232 ESB RS232 + * + * @{ + */ + +/** + * \file + * Header file for MSP430 RS232 driver. + * \author Adam Dunkels + * + */ +#ifndef __RS232_H__ +#define __RS232_H__ + + +#define RS232_19200 1 +#define RS232_38400 2 +#define RS232_57600 3 +#define RS232_115200 4 + +/** + * \brief Initialize the RS232 module + * + * This function is called from the boot up code to + * initalize the RS232 module. + */ +void rs232_init(void); + +/** + * \brief Set an input handler for incoming RS232 data + * \param f A pointer to a byte input handler + * + * This function sets the input handler for incoming RS232 + * data. The input handler function is called for every + * incoming data byte. The function is called from the + * RS232 interrupt handler, so care must be taken when + * implementing the input handler to avoid race + * conditions. + * + * The return value of the input handler affects the sleep + * mode of the CPU: if the input handler returns non-zero + * (true), the CPU is awakened to let other processing + * take place. If the input handler returns zero, the CPU + * is kept sleeping. + */ +void rs232_set_input(int (* f)(unsigned char)); + +/** + * \brief Configure the speed of the RS232 hardware + * \param speed The speed + * + * This function configures the speed of the RS232 + * hardware. The allowed parameters are RS232_19200, + * RS232_38400, RS232_57600, and RS232_115200. + */ +void rs232_set_speed(unsigned char speed); + +/** + * \brief Print a text string on RS232 + * \param str A pointer to the string that is to be printed + * + * This function prints a string to RS232. The string must + * be terminated by a null byte. The RS232 module must be + * correctly initalized and configured for this function + * to work. + */ +void rs232_print(char *text); + +/** + * \brief Print a character on RS232 + * \param c The character to be printed + * + * This function prints a character to RS232. The RS232 + * module must be correctly initalized and configured for + * this function to work. + */ +void rs232_send(char c); + +#endif /* __RS232_H__ */ + +/** @} */ /** @} */ diff --git a/platform/cooja/dev/vib-sensor.c b/platform/cooja/dev/vib-sensor.c new file mode 100644 index 000000000..3228c8e1e --- /dev/null +++ b/platform/cooja/dev/vib-sensor.c @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: vib-sensor.c,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#include "lib/sensors.h" +#include "dev/vib-sensor.h" +#include "lib/simEnvChange.h" + +const struct simInterface vib_interface; +const struct sensors_sensor vib_sensor; + +// COOJA variables +char simVibChanged; +char simVibIsActive; +char simVibValue = 0; + +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ + simVibIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static int +irq(void) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void +activate(void) +{ + simVibIsActive = 1; +} +/*---------------------------------------------------------------------------*/ +static void +deactivate(void) +{ + simVibIsActive = 0; +} +/*---------------------------------------------------------------------------*/ +static int +active(void) +{ + return simVibIsActive; +} +/*---------------------------------------------------------------------------*/ +static unsigned int +value(int type) +{ + return simVibValue; +} +/*---------------------------------------------------------------------------*/ +static int +configure(int type, void *c) +{ + return 0; +} +/*---------------------------------------------------------------------------*/ +static void * +status(int type) +{ + return NULL; +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + // Check if Vib value has changed + if (simVibIsActive && simVibChanged) { + simVibValue = !simVibValue; + + sensors_changed(&vib_sensor); + simVibChanged = 0; + } +} +/*---------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*---------------------------------------------------------------------------*/ + +SIM_INTERFACE(vib_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); + +SENSORS_SENSOR(vib_sensor, VIB_SENSOR, + init, irq, activate, deactivate, active, + value, configure, status); diff --git a/platform/cooja/dev/vib-sensor.h b/platform/cooja/dev/vib-sensor.h new file mode 100644 index 000000000..beaa05e5f --- /dev/null +++ b/platform/cooja/dev/vib-sensor.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: vib-sensor.h,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#ifndef __VIB_H__ +#define __VIB_H__ + +extern const struct sensors_sensor vib_sensor; + +#define VIB_SENSOR "Vibration sensor" + +#endif /* __VIB_H__ */ diff --git a/platform/cooja/lib/simEnvChange.c b/platform/cooja/lib/simEnvChange.c new file mode 100644 index 000000000..0aa5829f0 --- /dev/null +++ b/platform/cooja/lib/simEnvChange.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: simEnvChange.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#include +#include + +#include "lib/simEnvChange.h" + +// All registered interfaces +extern const struct simInterface *simInterfaces[]; + +int simProcessRunValue; +int simEtimerPending; +int simNextExpirationTime; + +void doActionsBeforeTick() { + // Poll all interfaces to do their thing before the tick + int i; + for(i = 0; simInterfaces[i] != NULL; ++i) { + simInterfaces[i]->doActionsBeforeTick(); + } +} + +void doActionsAfterTick() { + // Poll all interfaces to do their thing after the tick + int i; + for(i = 0; simInterfaces[i] != NULL; ++i) { + simInterfaces[i]->doActionsAfterTick(); + } +} diff --git a/platform/cooja/lib/simEnvChange.h b/platform/cooja/lib/simEnvChange.h new file mode 100644 index 000000000..15d4c7f4c --- /dev/null +++ b/platform/cooja/lib/simEnvChange.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: simEnvChange.h,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#ifndef __SIMENVCHANGE_H__ +#define __SIMENVCHANGE_H__ + +// Simulation interface structure +struct simInterface { + void (* doActionsBeforeTick) (void); + void (* doActionsAfterTick) (void); +}; + +// Variable for keeping the last process_run() return value +extern int simProcessRunValue; +extern int simEtimerPending; +extern int simNextExpirationTime; + +// Definition for registering an interface +#define SIM_INTERFACE(name, doActionsBeforeTick, doActionsAfterTick) \ +const struct simInterface name = { doActionsBeforeTick, doActionsAfterTick } + +// Definition for getting access to simulation interface +#define SIM_INTERFACE_NAME(name) \ +extern const struct simInterface name + +// Definition for creating all interface (from main file) +#define SIM_INTERFACES(...) \ +const struct simInterface *simInterfaces[] = {__VA_ARGS__, NULL}; + +// Functions which polls all interfaces +void doActionsBeforeTick(); +void doActionsAfterTick(); + +#endif /* __SIMENVCHANGE_H__ */ diff --git a/platform/cooja/node-id.h b/platform/cooja/node-id.h new file mode 100644 index 000000000..75c6b7436 --- /dev/null +++ b/platform/cooja/node-id.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2005, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * This file is part of the Configurable Sensor Network Application + * Architecture for sensor nodes running the Contiki operating system. + * + * $Id: node-id.h,v 1.1 2006/08/21 12:11:16 fros4943 Exp $ + * + * ----------------------------------------------------------------- + * + * Author : Adam Dunkels, Joakim Eriksson, Niclas Finne + * Created : 2005-12-09 + * Updated : $Date: 2006/08/21 12:11:16 $ + * $Revision: 1.1 $ + */ + +#ifndef __NODE_ID_H__ +#define __NODE_ID_H__ + +#include "dev/moteid.h" + +#define node_id simMoteID + +#endif /* __NODE_ID_H__ */ diff --git a/platform/cooja/sys/clock.c b/platform/cooja/sys/clock.c new file mode 100644 index 000000000..754a018f6 --- /dev/null +++ b/platform/cooja/sys/clock.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: clock.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#include "sys/clock.h" +#include "lib/simEnvChange.h" + +const struct simInterface clock_interface; + +// COOJA variables +clock_time_t simCurrentTime; + +/*-----------------------------------------------------------------------------------*/ +void +clock_init(void) +{ +} +/*-----------------------------------------------------------------------------------*/ +clock_time_t +clock_time(void) +{ + return simCurrentTime; +} +/*-----------------------------------------------------------------------------------*/ +void +clock_delay(unsigned int delay_time) +{ +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(clock_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/sys/log.c b/platform/cooja/sys/log.c new file mode 100644 index 000000000..fc3a5902a --- /dev/null +++ b/platform/cooja/sys/log.c @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: log.c,v 1.1 2006/08/21 12:11:20 fros4943 Exp $ + */ + +#include "sys/log.h" +#include "lib/simEnvChange.h" +#include + +const struct simInterface simlog_interface; + +// COOJA variables +char simLoggedData[1024]; +int simLoggedLength; +char simLoggedFlag; + +void simlog(const char *message); + +/*-----------------------------------------------------------------------------------*/ +void +log_message(const char *part1, const char *part2) +{ + simlog(part1); + simlog(part2); +} +/*-----------------------------------------------------------------------------------*/ +void +simlog(const char *message) +{ + memcpy(&simLoggedData[0] + simLoggedLength, &message[0], strlen(message)); + simLoggedLength += strlen(message); + simLoggedFlag = 1; +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ +} +/*-----------------------------------------------------------------------------------*/ + +SIM_INTERFACE(simlog_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/platform/cooja/testbutton.c b/platform/cooja/testbutton.c new file mode 100644 index 000000000..c11b350d2 --- /dev/null +++ b/platform/cooja/testbutton.c @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testbutton.c,v 1.1 2006/08/21 12:11:16 fros4943 Exp $ + */ + + +#include "contiki.h" +#include "sys/loader.h" + +#include + +#include "lib/list.h" +#include "lib/random.h" + +#include "net/uip.h" + +#include "lib/sensors.h" +#include "sys/log.h" +#include "dev/button-sensor.h" + + +PROCESS(button_process, "Button test process"); + +PROCESS_THREAD(button_process, ev, data) +{ + static int custom_counter = 0; + static char logMess[100]; + + PROCESS_BEGIN(); + + sprintf(logMess, "Starting Button test process (counter=%i)\n", custom_counter); + log_message(logMess, ""); + + while(1) { + PROCESS_WAIT_EVENT(); + + if (button_sensor.value(0)) { + custom_counter++; + + sprintf(logMess, "button> Button pressed (counter=%i)\n", custom_counter); + log_message(logMess, ""); + } + } + + PROCESS_END(); +} diff --git a/platform/cooja/testbutton.h b/platform/cooja/testbutton.h new file mode 100644 index 000000000..8bd966d86 --- /dev/null +++ b/platform/cooja/testbutton.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testbutton.h,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#ifndef __BUTTON_TEST_H__ +#define __BUTTON_TEST_H__ + +#include "contiki.h" + +PROCESS_NAME(button_test_process); + +#endif /* __BUTTON_TEST_H__ */ diff --git a/platform/cooja/testetimer.c b/platform/cooja/testetimer.c new file mode 100644 index 000000000..44b1cea91 --- /dev/null +++ b/platform/cooja/testetimer.c @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testetimer.c,v 1.1 2006/08/21 12:11:17 fros4943 Exp $ + */ + + +#include +#include "contiki.h" +#include "sys/loader.h" + +#include "lib/list.h" +#include "lib/random.h" + +#include "net/uip.h" + +#include "sys/etimer.h" +#include "sys/clock.h" + +#include "lib/sensors.h" +#include "sys/log.h" + + +PROCESS(etimer_test_process, "ETimer test process"); + +PROCESS_THREAD(etimer_test_process, ev, data) +{ + static struct etimer mytimer; + + static int custom_counter = 0; + static char logMess[100]; + + PROCESS_BEGIN(); + + etimer_set(&mytimer, 1111); + + sprintf(logMess, "Starting ETimer test process (counter=%i)\n", custom_counter); + log_message(logMess, ""); + + while(1) { + PROCESS_WAIT_EVENT(); + + if (etimer_expired(&mytimer)) { + custom_counter++; + sprintf(logMess, "etimer> Timed out(counter=%i)\n", custom_counter); + log_message(logMess, ""); + + etimer_restart(&mytimer); + } + } + + PROCESS_END(); +} diff --git a/platform/cooja/testetimer.h b/platform/cooja/testetimer.h new file mode 100644 index 000000000..46b2c1382 --- /dev/null +++ b/platform/cooja/testetimer.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testetimer.h,v 1.1 2006/08/21 12:11:17 fros4943 Exp $ + */ + +#ifndef __ETIMER_TEST_H__ +#define __ETIMER_TEST_H__ + +#include "contiki.h" + +PROCESS_NAME(etimer_test_process); + +#endif /* __ETIMER_TEST_H__ */ diff --git a/platform/cooja/testserial.c b/platform/cooja/testserial.c new file mode 100644 index 000000000..3d316948f --- /dev/null +++ b/platform/cooja/testserial.c @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testserial.c,v 1.1 2006/08/21 12:11:16 fros4943 Exp $ + */ + + +#include "contiki.h" +#include "sys/loader.h" + +#include + +#include "lib/list.h" +#include "lib/random.h" + +#include "net/uip.h" + +#include "lib/sensors.h" +#include "sys/log.h" +#include "dev/serial.h" +#include "dev/rs232.h" + + +PROCESS(serial_test_process, "Serial test process"); + +AUTOSTART_PROCESSES(&serial_test_process); + +PROCESS_THREAD(serial_test_process, ev, data) +{ + static struct etimer mytimer; + + PROCESS_BEGIN(); + + etimer_set(&mytimer, CLOCK_SECOND); + + /* Starts the serial process among other */ + serial_init(); + + log_message("serial> Starting test process\n", ""); + + while(1) { + PROCESS_WAIT_EVENT(); + + if (etimer_expired(&mytimer)) { + log_message("serial> Sending serial data now\n", ""); + etimer_restart(&mytimer); + rs232_print("GNU's not Unix\n"); + } + + if(ev == serial_event_message) { + log_message("serial> Message received: ", data); + } + } + + PROCESS_END(); +} diff --git a/platform/cooja/testserial.h b/platform/cooja/testserial.h new file mode 100644 index 000000000..d7c757350 --- /dev/null +++ b/platform/cooja/testserial.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: testserial.h,v 1.1 2006/08/21 12:11:18 fros4943 Exp $ + */ + +#ifndef __BUTTON_TEST_H__ +#define __BUTTON_TEST_H__ + +#include "contiki.h" + +PROCESS_NAME(button_test_process); + +#endif /* __BUTTON_TEST_H__ */ diff --git a/tools/cooja/build.xml b/tools/cooja/build.xml new file mode 100644 index 000000000..cd034b152 --- /dev/null +++ b/tools/cooja/build.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + +The COOJA Simulator + +> ant run + Starts COOJA simulator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/cooja/config/code_main_template b/tools/cooja/config/code_main_template new file mode 100644 index 000000000..74a738945 --- /dev/null +++ b/tools/cooja/config/code_main_template @@ -0,0 +1,219 @@ +/** + * \defgroup coojacore COOJA Simulator Core + * @{ + */ + +/** + * \file + * C code template for generating contiki source code files from COOJA + * Simulator. This file should not be compiled directly. + * \author + * Fredrik Osterlind + */ + +/*---------------------------------------------------------------------------*/ +/** + * \page coojacore COOJA Simulator Core + * + * COOJA Simulator Core ("the Core") represents the entire C code part of COOJA + * Simulator. + * This part shortly consists of simulated sensors and actuators and code for + * communicating with the Java part. + * + * Communication with the Core should always be handled via dedicated classes + * (MoteType), and never directly. MoteType works as an interface giving access + * to variable's values given their name (through CoreComm->Lib?). + * + */ + +#include +#include +#include +#include "contiki.h" +#include "contiki-net.h" +#include "contiki-lib.h" +#include "contiki-conf.h" +#include "sys/clock.h" + +#include "lib/simEnvChange.h" +#include "lib/sensors.h" +#include "net/uip.h" +#include "dev/radio-arch.h" +#include "sys/etimer.h" + +/* Declare all initialization processes */ +[PROCESS_DEFINITIONS] + +/* Declare all sensors */ +[SENSOR_DEFINITIONS] + +/* Declare all simulation interfaces */ +[INTERFACE_DEFINITIONS] + + +/* Create initialization process array */ +[PROCESS_ARRAY] + +/* Create sensor array */ +[SENSOR_ARRAY] + +/* Create simulation interfaces array */ +[INTERFACE_ARRAY] + +// Default network interface +static struct uip_fw_netif simNetworkIF = + {UIP_FW_NETIF(0,0,0,0, 0,0,0,0, simDoSend)}; + +/* + * referenceVar is used for comparing absolute and process relative memory. + * (this must not be static due to memory locations) + */ +int referenceVar; + +extern unsigned long _end; + +/*---------------------------------------------------------------------------*/ +/** + * \brief Initialize a mote by starting processes etc. + * + * This function initializes a mote by starting certain + * processes and setting up the environment. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_init(JNIEnv *env, jobject obj) +{ + /* Initialize random generator */ + random_init(0); + + /* Start process handler */ + process_init(); + + /* Start Contiki processes */ + procinit_init(); + + /* Initialize uIP */ + uip_init(); + uip_fw_init(); + + /* Register network interface */ + uip_fw_default(&simNetworkIF); + + /* Start user applications */ + autostart_start((struct process **) autostart_processes); + } +/*---------------------------------------------------------------------------*/ +/** + * \brief Get a segment from the process memory. + * \param start Start address of segment + * \param length Size of memory segment + * \return Java byte array containing a copy of memory segment. + * + * Fetches a memory segment from the process memory starting at + * (start), with size (length). This function does not perform + * ANY error checking, and the process may crash if addresses are + * not available/readable. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT jbyteArray JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_getMemory(JNIEnv *env, jobject obj, jint start, jint length) +{ + jbyteArray ret=(*env)->NewByteArray(env, length); + (*env)->SetByteArrayRegion(env, ret, 0, (size_t) length, (jbyte *) start); + + return (ret); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Replace a segment of the process memory with given byte array. + * \param start Start address of segment + * \param length Size of memory segment + * \param mem_arr Byte array contaning new memory + * + * Replaces a process memory segment with given byte array. + * This function does not perform ANY error checking, and the + * process may crash if addresses are not available/writable. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_setMemory(JNIEnv *env, jobject obj, jint start, jint length, jbyteArray mem_arr) +{ + jbyte *mem = (*env)->GetByteArrayElements(env, mem_arr, 0); + memcpy((void *) start, mem, length); + (*env)->ReleaseByteArrayElements(env, mem_arr, mem, 0); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Let mote execute one "block" of code (tick mote). + * + * Let mote defined by the active contiki processes and current + * process memory execute some program code. This code must not block + * or else this function will never return. A typical contiki + * process will return when it executes PROCESS_WAIT..() statements. + * + * Before the control is left to contiki processes, any messages + * from the Java part are handled. These may for example be + * incoming network data. After the contiki processes return control, + * messages to the Java part are also handled (those which may need + * special attention). + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT void JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_tick(JNIEnv *env, jobject obj) +{ + /* Let all simulation interfaces act first */ + doActionsBeforeTick(); + + /* Check if any e-timers are pending (save result for state decisions) */ + if (etimer_pending()) { + /* Poll etimers */ + etimer_request_poll(); + simEtimerPending = 1; + } else { + simEtimerPending = 0; + } + + /* Execute the contiki code (save process_run return value for state decisions) */ + simProcessRunValue = process_run(); + + /* Let all simulation interfaces act before returning to java */ + doActionsAfterTick(); + + /* Look for new e-timers */ + if (!simEtimerPending && etimer_pending()) { + /* Poll etimers */ + etimer_request_poll(); + simEtimerPending = 1; + } + + /* Save nearest event timer expiration time (0 if no timers) */ + simNextExpirationTime = etimer_next_expiration_time(); +} +/*---------------------------------------------------------------------------*/ +/** + * \brief Get the absolute memory address of a special variable. + * \return Absolute memory address. + * + * Returns the absolute memory address of a special variable + * "referenceVar". By comparing this address with the relative + * address (from the map file) for referenceVar, an runtime offset + * can be calculated. + * + * This is a JNI function and should only be called via the + * responsible Java part (MoteType.java). + */ +JNIEXPORT jint JNICALL +Java_se_sics_cooja_corecomm_[CLASS_NAME]_getReferenceAbsAddr(JNIEnv *env, jobject obj) +{ + return (jint) &referenceVar; +} + +/** @} */ diff --git a/tools/cooja/config/cooja_default.config b/tools/cooja/config/cooja_default.config new file mode 100644 index 000000000..d7c76784b --- /dev/null +++ b/tools/cooja/config/cooja_default.config @@ -0,0 +1,34 @@ + +se.sics.cooja.interfaces.Battery.INFINITE_ENERGY_bool = false +se.sics.cooja.interfaces.Battery.INITIAL_ENERGY_mQ = 13500000 +se.sics.cooja.interfaces.Battery.CPU_AWAKE_mA = 1.49 +se.sics.cooja.interfaces.Battery.CPU_LPM_mA = 1.34 + +se.sics.cooja.contikimote.interfaces.ContikiRS232.CONSUMPTION_PER_CHAR_mQ = 1 +se.sics.cooja.contikimote.interfaces.ContikiRS232.EXTERNAL_INTERRUPT_bool = true + +se.sics.cooja.contikimote.interfaces.ContikiLED.GREEN_LED_CONSUMPTION_mA = 5.69 +se.sics.cooja.contikimote.interfaces.ContikiLED.YELLOW_LED_CONSUMPTION_mA = 5.69 +se.sics.cooja.contikimote.interfaces.ContikiLED.RED_LED_CONSUMPTION_mA = 5.69 + +se.sics.cooja.contikimote.interfaces.ContikiButton.EXTERNAL_INTERRUPT_bool = true + +se.sics.cooja.contikimote.interfaces.ContikiBeeper.BEEP_CONSUMPTION_mQ = 1.669 + +se.sics.cooja.contikimote.interfaces.ContikiPIR.ACTIVE_CONSUMPTION_mA = 0.4 +se.sics.cooja.contikimote.interfaces.ContikiPIR.EXTERNAL_INTERRUPT_bool = true + +se.sics.cooja.contikimote.interfaces.ContikiRadio.ACTIVE_CONSUMPTION_mA = 5 +se.sics.cooja.contikimote.interfaces.ContikiRadio.EXTERNAL_INTERRUPT_bool = true + +se.sics.cooja.contikimote.interfaces.ContikiVib.ACTIVE_CONSUMPTION_mA = 1.58 +se.sics.cooja.contikimote.interfaces.ContikiVib.EXTERNAL_INTERRUPT_bool = true + +se.sics.cooja.contikimote.ContikiMoteType.MOTE_INTERFACES = se.sics.cooja.interfaces.Position se.sics.cooja.interfaces.Battery se.sics.cooja.contikimote.interfaces.ContikiVib se.sics.cooja.contikimote.interfaces.ContikiMoteID se.sics.cooja.contikimote.interfaces.ContikiRS232 se.sics.cooja.contikimote.interfaces.ContikiBeeper se.sics.cooja.contikimote.interfaces.ContikiIPAddress se.sics.cooja.contikimote.interfaces.ContikiRadio se.sics.cooja.contikimote.interfaces.ContikiButton se.sics.cooja.contikimote.interfaces.ContikiPIR se.sics.cooja.contikimote.interfaces.ContikiClock se.sics.cooja.contikimote.interfaces.ContikiLED se.sics.cooja.contikimote.interfaces.ContikiLog +se.sics.cooja.contikimote.ContikiMoteType.C_SOURCES = +se.sics.cooja.GUI.MOTETYPES = se.sics.cooja.contikimote.ContikiMoteType se.sics.cooja.motes.DummyMoteType +se.sics.cooja.GUI.PLUGINS = se.sics.cooja.plugins.VisState se.sics.cooja.plugins.VisBattery se.sics.cooja.plugins.VisTraffic se.sics.cooja.plugins.LogListener se.sics.cooja.plugins.MoteInformation se.sics.cooja.plugins.MoteInterfaceViewer se.sics.cooja.plugins.VariableWatcher +se.sics.cooja.GUI.IP_DISTRIBUTORS = se.sics.cooja.ipdistributors.RandomIPDistributor se.sics.cooja.ipdistributors.SpatialIPDistributor se.sics.cooja.ipdistributors.IdIPDistributor +se.sics.cooja.GUI.POSITIONERS = se.sics.cooja.positioners.RandomPositioner se.sics.cooja.positioners.LinearPositioner se.sics.cooja.positioners.EllipsePositioner +se.sics.cooja.GUI.RADIOMEDIUMS = se.sics.cooja.radiomediums.StandardRadioMedium se.sics.cooja.radiomediums.SilentRadioMedium + diff --git a/tools/cooja/config/external_tools_linux.config b/tools/cooja/config/external_tools_linux.config new file mode 100644 index 000000000..d9f5b07df --- /dev/null +++ b/tools/cooja/config/external_tools_linux.config @@ -0,0 +1,17 @@ +PATH_CONTIKI = ../../.. +PATH_COOJA_CORE_RELATIVE = /platform/cooja +PATH_MAKE = make +PATH_LINKER = ld +PATH_SHELL = sh +PATH_C_COMPILER = gcc +CMD_GREP_PROCESSES = grep "^PROCESS_THREAD([^,]*,[^,]*,[^)]*)" -o -H +REGEXP_PARSE_PROCESSES = ([^/]*.c):PROCESS_THREAD\\(([^,]*),[^,]*,[^)]*\\) +CMD_GREP_INTERFACES = grep "^SIM_INTERFACE([^,]*," -o -d skip -D skip -H -r +REGEXP_PARSE_INTERFACES = ([^/]*.c):SIM_INTERFACE\\(([^,]*), +CMD_GREP_SENSORS = grep "^SENSORS_SENSOR([^,]*," -o -d skip -D skip -H -r +REGEXP_PARSE_SENSORS = ([^/]*.c):SENSORS_SENSOR\\(([^,]*), +LINKER_ARGS_1 = +LINKER_ARGS_2 = +COMPILER_ARGS = +CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process;tcpip_process;ether_process;uip_fw_process +CONTIKI_MAIN_TEMPLATE_FILENAME = code_main_template \ No newline at end of file diff --git a/tools/cooja/config/external_tools_win32.config b/tools/cooja/config/external_tools_win32.config new file mode 100644 index 000000000..bde282ba5 --- /dev/null +++ b/tools/cooja/config/external_tools_win32.config @@ -0,0 +1,17 @@ +PATH_CONTIKI = ../../.. +PATH_COOJA_CORE_RELATIVE = /platform/cooja +PATH_MAKE = make +PATH_LINKER = ld +PATH_SHELL = sh +PATH_C_COMPILER = gcc +CMD_GREP_PROCESSES = grep '^PROCESS_THREAD([^,]*,[^,]*,[^)]*)' -o -d skip -D skip -H -r +REGEXP_PARSE_PROCESSES = ([^/]*.c):PROCESS_THREAD\\(([^,]*),[^,]*,[^)]*\\) +CMD_GREP_INTERFACES = grep '^SIM_INTERFACE([^,]*,' -o -d skip -D skip -H -r +REGEXP_PARSE_INTERFACES = ([^/]*.c):SIM_INTERFACE\\(([^,]*), +CMD_GREP_SENSORS = grep '^SENSORS_SENSOR([^,]*,' -o -d skip -D skip -H -r +REGEXP_PARSE_SENSORS = ([^/]*.c):SENSORS_SENSOR\\(([^,]*), +LINKER_ARGS_1 = --add-stdcall-alias /usr/lib/mingw/dllcrt2.o +LINKER_ARGS_2 = -L/usr/lib/mingw -lmingw32 -lmingwex -lmsvcrt +COMPILER_ARGS = -mno-cygwin -I'C:/Program Files/Java/jdk1.5.0_06/include' -I'C:/Program Files/Java/jdk1.5.0_06/include/win32' +CONTIKI_STANDARD_PROCESSES = sensors_process;etimer_process;tcpip_process;ether_process;uip_fw_process +CONTIKI_MAIN_TEMPLATE_FILENAME = code_main_template diff --git a/tools/cooja/config/log4j_config.xml b/tools/cooja/config/log4j_config.xml new file mode 100644 index 000000000..5a524e7a5 --- /dev/null +++ b/tools/cooja/config/log4j_config.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/cooja/examples/jni_test/build.xml b/tools/cooja/examples/jni_test/build.xml new file mode 100644 index 000000000..fc6c688bd --- /dev/null +++ b/tools/cooja/examples/jni_test/build.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + +Win32 cygwin users may try: + COMPILER_ARGS= -mno-cygwin -I....../jdk1.5.0/include -I....../jdk1.5.0/include/win32 + LINKER_ARGS_1 = --add-stdcall-alias /usr/lib/mingw/dllcrt2.o + LINKER_ARGS_2 = -L/usr/lib/mingw -lmingw32 -lmingwex -lmsvcrt + + Only for level 1, try the following compiler arguments: + COMPILER_ARGS= -mno-cygwin -I....../jdk1.5.0/include -I....../jdk1.5.0/include/win32 -Wl,--add-stdcall + + + + + +The COOJA Simulator - JNI Tests +There examples may help understand errors during compilation from inside COOJA. +For some examples; "ant help". + + +ant level1 + Runs JNI test level 1: + [compilation test] + Compiles level1.c to level1.library, using only c compiler. + Java class loads the library and calls a simple native function. + +ant level2 + Runs JNI test level 2: + [compilation test] + Compiles level2.c to level2.library, using both c compiler and linker. + Java class loads the library and calls a simple native function. + +ant level3 + Runs JNI test level 3: + [map file parsing test] + Compiles java + c. + The map file is parsed, and information about data+bss sections is outputted. + +ant level4 + Runs JNI test level 4: + [fetching reference var] + Calculates offset between relative (mapfile) and absolute memory. + A simple native function increases two counters (from both data and bss sections). + +ant level5 + Runs JNI test level 5: + [fetches and restores memory segments - the final test] + A simple native function increases two counters (from both data and bss sections). + The current memory (data+bss sections) is fetched and restored between function calls. + The counters should be restored with the memory! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/cooja/examples/jni_test/level1/Level1.java b/tools/cooja/examples/jni_test/level1/Level1.java new file mode 100644 index 000000000..b8027ca32 --- /dev/null +++ b/tools/cooja/examples/jni_test/level1/Level1.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Level1.java,v 1.1 2006/08/21 12:13:06 fros4943 Exp $ + */ + +import java.io.*; + +public class Level1 { + + static { + System.err.println("JAVA Level1 static> loading library now"); + System.load(new File("level1.library").getAbsolutePath()); + System.err.println("JAVA Level1 static> done loading library"); + } + + private native void test(); + + public Level1() { + System.err.println("JAVA Level1 constructor()> running native test function"); + test(); + } + + public static void main(String[] args) { + new Level1(); + } + +} diff --git a/tools/cooja/examples/jni_test/level1/level1.c b/tools/cooja/examples/jni_test/level1/level1.c new file mode 100644 index 000000000..37f035c9a --- /dev/null +++ b/tools/cooja/examples/jni_test/level1/level1.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: level1.c,v 1.1 2006/08/21 12:13:06 fros4943 Exp $ + */ + +#include +#include + +JNIEXPORT void JNICALL +Java_Level1_test(JNIEnv *env, jobject obj) +{ + fprintf(stderr, "C test()> Level 1 OK!\n"); + fflush(stderr); +} diff --git a/tools/cooja/examples/jni_test/level2/Level2.java b/tools/cooja/examples/jni_test/level2/Level2.java new file mode 100644 index 000000000..0b40ffb08 --- /dev/null +++ b/tools/cooja/examples/jni_test/level2/Level2.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Level2.java,v 1.1 2006/08/21 12:13:00 fros4943 Exp $ + */ + +import java.io.*; + +public class Level2 { + + static { + System.err.println("JAVA Level2 static> loading library now"); + System.load(new File("level2.library").getAbsolutePath()); + System.err.println("JAVA Level2 static> done loading library"); + } + + private native void test(); + + public Level2() { + System.err.println("JAVA Level2 constructor()> running native test function"); + test(); + } + + public static void main(String[] args) { + new Level2(); + } + +} diff --git a/tools/cooja/examples/jni_test/level2/level2.c b/tools/cooja/examples/jni_test/level2/level2.c new file mode 100644 index 000000000..7e8a6fe5b --- /dev/null +++ b/tools/cooja/examples/jni_test/level2/level2.c @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: level2.c,v 1.1 2006/08/21 12:13:00 fros4943 Exp $ + */ + +#include +#include + +JNIEXPORT void JNICALL +Java_Level2_test(JNIEnv *env, jobject obj) +{ + fprintf(stderr, "C test()> Level 2 OK!\n"); + fflush(stderr); +} diff --git a/tools/cooja/examples/jni_test/level3/Level3.java b/tools/cooja/examples/jni_test/level3/Level3.java new file mode 100644 index 000000000..12af4de7a --- /dev/null +++ b/tools/cooja/examples/jni_test/level3/Level3.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Level3.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +import java.io.*; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Level3 { + + final static private String bssSectionAddrRegExp = + "^.bss[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String bssSectionSizeRegExp = + "^.bss[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String dataSectionAddrRegExp = + "^.data[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String dataSectionSizeRegExp = + "^.data[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + + static { + System.load(new File("level3.library").getAbsolutePath()); + } + + private native void test(); + + public Level3() { + File mapFile = new File("level3.map"); + + // Check that map file exists + if (!mapFile.exists()) { + System.err.println("No map file could be loaded"); + System.exit(1); + } + + System.err.println("Loading map file"); + Vector mapContents = loadMapFile(mapFile); + + System.err.println("Parsing map file"); + int relDataSectionAddr = loadRelDataSectionAddr(mapContents); + int dataSectionSize = (int) loadDataSectionSize(mapContents); + int relBssSectionAddr = loadRelBssSectionAddr(mapContents); + int bssSectionSize = (int) loadBssSectionSize(mapContents); + + System.err.println("Found relative data section address: 0x" + Integer.toHexString(relDataSectionAddr)); + System.err.println("Found data section size: 0x" + Integer.toHexString(dataSectionSize)); + System.err.println("Found relative bss section address: 0x" + Integer.toHexString(relBssSectionAddr)); + System.err.println("Found bss section address: 0x" + Integer.toHexString(bssSectionSize)); + + test(); + } + + private static Vector loadMapFile(File mapFile) { + Vector mapContents = new Vector(); + + try { + BufferedReader in = + new BufferedReader( + new InputStreamReader( + new FileInputStream(mapFile))); + + while (in.ready()) + { + mapContents.add(in.readLine()); + } + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e); + return null; + } catch (IOException e) { + System.err.println("IO error: " + e); + return null; + } + + return mapContents; + } + + private static int loadRelDataSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadDataSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadRelBssSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadBssSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static String getFirstMatchGroup(Vector lines, String regexp, int groupNr) { + Pattern pattern = Pattern.compile(regexp); + for (int i=0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + return matcher.group(groupNr); + } + } + return null; + } + + + public static void main(String[] args) { + new Level3(); + } + +} diff --git a/tools/cooja/examples/jni_test/level3/level3.c b/tools/cooja/examples/jni_test/level3/level3.c new file mode 100644 index 000000000..aa2e73731 --- /dev/null +++ b/tools/cooja/examples/jni_test/level3/level3.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: level3.c,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +#include +#include + +int initialized_counter=1; +int uninitialized_counter; + +JNIEXPORT void JNICALL +Java_Level3_test(JNIEnv *env, jobject obj) +{ + fprintf(stderr, "C test()> Level 3 OK!\n"); + fflush(stderr); +} diff --git a/tools/cooja/examples/jni_test/level4/Level4.java b/tools/cooja/examples/jni_test/level4/Level4.java new file mode 100644 index 000000000..bc34dfe3a --- /dev/null +++ b/tools/cooja/examples/jni_test/level4/Level4.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Level4.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +import java.io.*; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Level4 { + + static { + System.load(new File("level4.library").getAbsolutePath()); + } + + final static private String bssSectionAddrRegExp = + "^.bss[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String bssSectionSizeRegExp = + "^.bss[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String dataSectionAddrRegExp = + "^.data[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String dataSectionSizeRegExp = + "^.data[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String varAddressRegExpPrefix = + "^[ \t]*0x([0-9A-Fa-f]*)[ \t]*"; + final static private String varAddressRegExpSuffix = + "[ \t]*$"; + final static private String varNameRegExp = + "^[ \t]*(0x[0-9A-Fa-f]*)[ \t]*([^ ]*)[ \t]*$"; + final static private String varSizeRegExpPrefix = + "^"; + final static private String varSizeRegExpSuffix = + "[ \t]*(0x[0-9A-Fa-f]*)[ \t]*[^ ]*[ \t]*$"; + + private native void doCount(); + private native int getRefAddress(); + + public Level4() { + File mapFile = new File("level4.map"); + + // Check that map file exists + if (!mapFile.exists()) { + System.err.println("No map file could be loaded"); + System.exit(1); + } + + Vector mapContents = loadMapFile(mapFile); + + int relDataSectionAddr = loadRelDataSectionAddr(mapContents); + int dataSectionSize = (int) loadDataSectionSize(mapContents); + int relBssSectionAddr = loadRelBssSectionAddr(mapContents); + int bssSectionSize = (int) loadBssSectionSize(mapContents); + + int referenceAddress = getRefAddress(); + System.err.println("Reference address: 0x" + Integer.toHexString(referenceAddress)); + + int offsetRelToAbs = referenceAddress - getRelVarAddr(mapContents, "ref_var"); + System.err.println("Offset relative-absolute: 0x" + Integer.toHexString(offsetRelToAbs)); + + doCount(); + doCount(); + doCount(); + doCount(); + doCount(); + + System.err.println("Level 4 OK!"); + } + + private static int getRelVarAddr(Vector mapContents, String varName) { + String regExp = varAddressRegExpPrefix + varName + varAddressRegExpSuffix; + String retString = getFirstMatchGroup(mapContents, regExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static Vector loadMapFile(File mapFile) { + Vector mapContents = new Vector(); + + try { + BufferedReader in = + new BufferedReader( + new InputStreamReader( + new FileInputStream(mapFile))); + + while (in.ready()) + { + mapContents.add(in.readLine()); + } + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e); + return null; + } catch (IOException e) { + System.err.println("IO error: " + e); + return null; + } + + return mapContents; + } + + private static int loadRelDataSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadDataSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadRelBssSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadBssSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static String getFirstMatchGroup(Vector lines, String regexp, int groupNr) { + Pattern pattern = Pattern.compile(regexp); + for (int i=0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + return matcher.group(groupNr); + } + } + return null; + } + + + public static void main(String[] args) { + new Level4(); + } + +} diff --git a/tools/cooja/examples/jni_test/level4/level4.c b/tools/cooja/examples/jni_test/level4/level4.c new file mode 100644 index 000000000..27a95df81 --- /dev/null +++ b/tools/cooja/examples/jni_test/level4/level4.c @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: level4.c,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +#include +#include + +int ref_var; + +int initialized_counter=1; +int uninitialized_counter; + +JNIEXPORT void JNICALL +Java_Level4_doCount(JNIEnv *env, jobject obj) +{ + fprintf(stderr, ">> DATA_counter=\t%i\tBSS_counter=\t%i\n", initialized_counter++, uninitialized_counter++); + fflush(stderr); +} +JNIEXPORT jint JNICALL +Java_Level4_getRefAddress(JNIEnv *env, jobject obj) +{ + return (jint) &ref_var; +} diff --git a/tools/cooja/examples/jni_test/level5/Level5.java b/tools/cooja/examples/jni_test/level5/Level5.java new file mode 100644 index 000000000..69b9a0879 --- /dev/null +++ b/tools/cooja/examples/jni_test/level5/Level5.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Level5.java,v 1.1 2006/08/21 12:13:10 fros4943 Exp $ + */ + +import java.io.*; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Level5 { + + static { + System.load(new File("level5.library").getAbsolutePath()); + } + + final static private String bssSectionAddrRegExp = + "^.bss[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String bssSectionSizeRegExp = + "^.bss[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String dataSectionAddrRegExp = + "^.data[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String dataSectionSizeRegExp = + "^.data[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String varAddressRegExpPrefix = + "^[ \t]*0x([0-9A-Fa-f]*)[ \t]*"; + final static private String varAddressRegExpSuffix = + "[ \t]*$"; + final static private String varNameRegExp = + "^[ \t]*(0x[0-9A-Fa-f]*)[ \t]*([^ ]*)[ \t]*$"; + final static private String varSizeRegExpPrefix = + "^"; + final static private String varSizeRegExpSuffix = + "[ \t]*(0x[0-9A-Fa-f]*)[ \t]*[^ ]*[ \t]*$"; + + private native void doCount(); + private native int getRefAddress(); + private native byte[] getMemory(int start, int length); + private native void setMemory(int start, int length, byte[] mem); + + public Level5() { + File mapFile = new File("level5.map"); + + // Check that map file exists + if (!mapFile.exists()) { + System.err.println("No map file could be loaded"); + System.exit(1); + } + + Vector mapContents = loadMapFile(mapFile); + + int relDataSectionAddr = loadRelDataSectionAddr(mapContents); + int dataSectionSize = (int) loadDataSectionSize(mapContents); + int relBssSectionAddr = loadRelBssSectionAddr(mapContents); + int bssSectionSize = (int) loadBssSectionSize(mapContents); + + int referenceAddress = getRefAddress(); + + int offsetRelToAbs = referenceAddress - getRelVarAddr(mapContents, "ref_var"); + + System.err.println("\n\n--- RUNNING DO_COUNT 5 TIMES ---"); + doCount(); + doCount(); + doCount(); + doCount(); + doCount(); + System.err.println("\n\n--- FETCHING AND SAVING MEMORY ---"); + byte[] savedDataSection = getMemory(relDataSectionAddr + offsetRelToAbs, dataSectionSize); + byte[] savedBssSection = getMemory(relBssSectionAddr + offsetRelToAbs, bssSectionSize); + System.err.println("data section size:\t" + savedDataSection.length + " = " + "0x" + Integer.toHexString(savedDataSection.length)); + System.err.println("bss section size:\t" + savedBssSection.length + " = " + "0x" + Integer.toHexString(savedBssSection.length)); + + System.err.println("\n\n--- RUNNING DO_COUNT 3 TIMES ---"); + doCount(); + doCount(); + doCount(); + + System.err.println("\n\n--- RESTORING MEMORY: DATA ---"); + setMemory(relDataSectionAddr + offsetRelToAbs, dataSectionSize, savedDataSection); + + System.err.println("\n\n--- RUNNING DO_COUNT 3 TIMES ---"); + doCount(); + doCount(); + doCount(); + + System.err.println("\n\n--- RESTORING MEMORY: BSS ---"); + setMemory(relBssSectionAddr + offsetRelToAbs, bssSectionSize, savedBssSection); + + System.err.println("\n\n--- RUNNING DO_COUNT 3 TIMES ---"); + doCount(); + doCount(); + doCount(); + + System.err.println("Level 5 OK!"); + } + + private static int getRelVarAddr(Vector mapContents, String varName) { + String regExp = varAddressRegExpPrefix + varName + varAddressRegExpSuffix; + String retString = getFirstMatchGroup(mapContents, regExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static Vector loadMapFile(File mapFile) { + Vector mapContents = new Vector(); + + try { + BufferedReader in = + new BufferedReader( + new InputStreamReader( + new FileInputStream(mapFile))); + + while (in.ready()) + { + mapContents.add(in.readLine()); + } + } catch (FileNotFoundException e) { + System.err.println("File not found: " + e); + return null; + } catch (IOException e) { + System.err.println("IO error: " + e); + return null; + } + + return mapContents; + } + + private static int loadRelDataSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadDataSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadRelBssSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static int loadBssSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else return 0; + } + + private static String getFirstMatchGroup(Vector lines, String regexp, int groupNr) { + Pattern pattern = Pattern.compile(regexp); + for (int i=0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + return matcher.group(groupNr); + } + } + return null; + } + + + + public static void main(String[] args) { + new Level5(); + } + +} diff --git a/tools/cooja/examples/jni_test/level5/level5.c b/tools/cooja/examples/jni_test/level5/level5.c new file mode 100644 index 000000000..7acd7a956 --- /dev/null +++ b/tools/cooja/examples/jni_test/level5/level5.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: level5.c,v 1.1 2006/08/21 12:13:10 fros4943 Exp $ + */ + +#include +#include +#include + +int ref_var; + +int initialized_counter=1; +int uninitialized_counter; + +JNIEXPORT void JNICALL +Java_Level5_doCount(JNIEnv *env, jobject obj) +{ + fprintf(stderr, ">> DATA_counter=\t%i\tBSS_counter=\t%i\n", initialized_counter++, uninitialized_counter++); + fflush(stderr); +} +JNIEXPORT jint JNICALL +Java_Level5_getRefAddress(JNIEnv *env, jobject obj) +{ + return (jint) &ref_var; +} + +JNIEXPORT jbyteArray JNICALL +Java_Level5_getMemory(JNIEnv *env, jobject obj, jint start, jint length) +{ + jbyteArray ret=(*env)->NewByteArray(env, length); + (*env)->SetByteArrayRegion(env, ret, 0, (size_t) length, (jbyte *) start); + + return (ret); +} + +JNIEXPORT void JNICALL +Java_Level5_setMemory(JNIEnv *env, jobject obj, jint start, jint length, jbyteArray mem_arr) +{ + jbyte *mem = (*env)->GetByteArrayElements(env, mem_arr, 0); + memcpy((void *) start, mem, length); + (*env)->ReleaseByteArrayElements(env, mem_arr, mem, 0); +} diff --git a/tools/cooja/examples/userplatform_debug/cooja.config b/tools/cooja/examples/userplatform_debug/cooja.config new file mode 100644 index 000000000..d59479393 --- /dev/null +++ b/tools/cooja/examples/userplatform_debug/cooja.config @@ -0,0 +1 @@ +se.sics.cooja.GUI.PLUGINS = + MoteDebugger diff --git a/tools/cooja/examples/userplatform_debug/java/MoteDebugger.java b/tools/cooja/examples/userplatform_debug/java/MoteDebugger.java new file mode 100644 index 000000000..6fa7becaa --- /dev/null +++ b/tools/cooja/examples/userplatform_debug/java/MoteDebugger.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteDebugger.java,v 1.1 2006/08/21 12:13:14 fros4943 Exp $ + */ + +import java.awt.event.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.*; +import org.apache.log4j.Logger; +import java.io.*; +import java.lang.management.*; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMote; +import se.sics.cooja.contikimote.ContikiMoteType; + +/** + * Mote debugger lets a user debug a mote using an external debugger. + * + * It executes the external program 'gdb' and sets up breakpoints + * at the entry of the tick function. + * + * The selected mote is then set to state active and ticked. + * + * OBSERVE! Experimental code. Not fully tested yet! + * + * @author Fredrik Osterlind + */ +@ClassDescription("Debug using GDB") +@VisPluginType(VisPluginType.MOTE_PLUGIN) +public class MoteDebugger extends VisPlugin {; + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(MoteDebugger.class); + private ContikiMote moteToDebug; + + /** + * Creates a new VisDebug. + * @param mote Contiki mote to debug next tick + */ + public MoteDebugger(Mote mote) { + super("VisDebug (" + mote + ")"); + this.moteToDebug = (ContikiMote) mote; + + JButton debugButton = new JButton("Debug now"); + debugButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + logger.warn("OBSERVE! This is experimental code"); + + logger.info("Getting JVM pid"); + RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean(); + String runtimeName = rt.getName(); + int pid = 1; + + String pidExtraction = "^([0-9]*)[^$]*$"; + Pattern pattern = Pattern.compile(pidExtraction); + Matcher matcher = pattern.matcher(runtimeName); + if (!matcher.find()) { + logger.fatal("Could not determine pid, aborting"); + return; + } + + pid = Integer.parseInt(matcher.group(1)); + if (pid <= 0) { + logger.fatal("Pid seems to be strange, aborting. pid=" + pid); + return; + } + + logger.info("Extracted PID=" + pid); + + logger.info("Checking that source code file exists.."); + File sourceFile = new File("obj_cooja/" + moteToDebug.getType().getIdentifier() + ".c"); + if (!sourceFile.exists()) { + logger.fatal("Can't find source file: " + sourceFile); + return; + } + + logger.info("Source file ok: " + sourceFile); + + + logger.info("Determining function name to break at (entry of tick)"); + String libName = ((ContikiMoteType) moteToDebug.getType()).getLibraryClassName(); + + String functionName = "Java." + CoreComm.class.getPackage().getName() + ".corecomm." + libName + ".tick"; + functionName = functionName.replaceAll("\\.", "_"); + + logger.info("Function name is: " + functionName); + + logger.info("Creating temporary file .tmp with initial commands"); + File tmpFile = new File("obj_cooja/" + ".tmp"); + if (tmpFile.exists()) { + tmpFile.delete(); + } + try { + BufferedWriter tmpStream = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream( + tmpFile))); + tmpStream.write("break " + functionName + "\n"); + tmpStream.write("cont\n"); + tmpStream.close(); + } catch (Exception ex) { + logger.fatal("Could not create temporary command file: " + tmpFile); + return; + } + + logger.info("Command file created ok: " + tmpFile.getName()); + + logger.info("Starting external GDB"); + logger.info("> GDB must be exited before control is returned to COOJA"); + logger.info("> Use command 'quit' followed by y to exit GDB"); + + Process gdbProcess = null; + try { + gdbProcess = Runtime.getRuntime().exec("xterm -e gdb" + + " -nw -quiet " + + " --pid=" + pid + + " -x " + "obj_cooja/" + tmpFile.getName() + ); + + logger.info("Sleeping 2500 ms while starting up GDB"); + Thread.sleep(2500); + logger.info("Ticking chosen mote now! (setting state to active)"); + moteToDebug.setState(Mote.STATE_ACTIVE); + moteToDebug.tick(GUI.currentSimulation.getSimulationTime()); + + gdbProcess.waitFor(); + } catch (Exception ex) { + logger.fatal("Exception while starting gdb, aborting"); + } + + logger.debug("GDB terminated with exit code: " + gdbProcess.exitValue()); + } + }); + + add(debugButton); + setSize(250, 80); + } + + public void closePlugin() { + } + +} diff --git a/tools/cooja/examples/userplatform_new_apps/app1.c b/tools/cooja/examples/userplatform_new_apps/app1.c new file mode 100644 index 000000000..17a1fedfa --- /dev/null +++ b/tools/cooja/examples/userplatform_new_apps/app1.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: app1.c,v 1.1 2006/08/21 12:13:09 fros4943 Exp $ + */ + +#include "contiki.h" +#include "sys/loader.h" + +#include + +#include "lib/list.h" +#include "lib/random.h" + +#include "net/uip.h" + +#include "lib/sensors.h" +#include "sys/log.h" +#include "dev/button-sensor.h" + + +PROCESS(dummy_process_1, "Dummy process 1"); + +PROCESS_THREAD(dummy_process_1, ev, data) +{ + PROCESS_BEGIN(); + + log_message("Dummy process 1 started", ""); + + while(1) { + PROCESS_WAIT_EVENT(); + log_message("Dummy process 1 received event", ""); + } + + PROCESS_END(); +} diff --git a/tools/cooja/examples/userplatform_new_apps/app2.c b/tools/cooja/examples/userplatform_new_apps/app2.c new file mode 100644 index 000000000..3ed5fd558 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_apps/app2.c @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: app2.c,v 1.1 2006/08/21 12:13:09 fros4943 Exp $ + */ + +#include "contiki.h" +#include "sys/loader.h" + +#include + +#include "lib/list.h" +#include "lib/random.h" + +#include "net/uip.h" + +#include "lib/sensors.h" +#include "sys/log.h" +#include "dev/button-sensor.h" + + +PROCESS(dummy_process_2, "Dummy process 2"); + +PROCESS_THREAD(dummy_process_2, ev, data) +{ + PROCESS_BEGIN(); + + log_message("Dummy process 2 started", ""); + + while(1) { + PROCESS_WAIT_EVENT(); + log_message("Dummy process 2 received event", ""); + } + + PROCESS_END(); +} diff --git a/tools/cooja/examples/userplatform_new_apps/cooja.config b/tools/cooja/examples/userplatform_new_apps/cooja.config new file mode 100644 index 000000000..d2cf1d2e6 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_apps/cooja.config @@ -0,0 +1 @@ +se.sics.cooja.contikimote.ContikiMoteType.C_SOURCES = + app1.c app2.c diff --git a/tools/cooja/examples/userplatform_new_interface/cooja.config b/tools/cooja/examples/userplatform_new_interface/cooja.config new file mode 100644 index 000000000..4cafbd62a --- /dev/null +++ b/tools/cooja/examples/userplatform_new_interface/cooja.config @@ -0,0 +1,2 @@ +se.sics.cooja.contikimote.ContikiMoteType.MOTE_INTERFACES = + DummyInterface +se.sics.cooja.contikimote.ContikiMoteType.C_SOURCES = + dummy_intf.c diff --git a/tools/cooja/examples/userplatform_new_interface/dummy_intf.c b/tools/cooja/examples/userplatform_new_interface/dummy_intf.c new file mode 100644 index 000000000..ebbfcfaac --- /dev/null +++ b/tools/cooja/examples/userplatform_new_interface/dummy_intf.c @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: dummy_intf.c,v 1.1 2006/08/21 12:13:13 fros4943 Exp $ + */ + +#include "dummy_intf.h" +#include "lib/simEnvChange.h" +#include + +const struct simInterface beep_interface; + +// COOJA variables (shared between Java and C) +char simDummyVar; + +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsBeforeTick(void) +{ + fprintf(stderr, "Core (C) dummy interface acts BEFORE mote tick\n"); +} +/*-----------------------------------------------------------------------------------*/ +static void +doInterfaceActionsAfterTick(void) +{ + fprintf(stderr, "Core (C) dummy interface acts AFTER mote tick\n"); +} +/*-----------------------------------------------------------------------------------*/ + +// Register this as an available interface +SIM_INTERFACE(dummy_interface, + doInterfaceActionsBeforeTick, + doInterfaceActionsAfterTick); diff --git a/tools/cooja/examples/userplatform_new_interface/dummy_intf.h b/tools/cooja/examples/userplatform_new_interface/dummy_intf.h new file mode 100644 index 000000000..d76594543 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_interface/dummy_intf.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: dummy_intf.h,v 1.1 2006/08/21 12:13:13 fros4943 Exp $ + */ + +#ifndef __DUMMY_INTF_H__ +#define __DUMMY_INTF_H__ + +// Interface needs to include something? + +#define DUMMY_NR_1 1 +#define DUMMY_NR_2 2 + +#endif /* __DUMMY_INTF_H__ */ diff --git a/tools/cooja/examples/userplatform_new_interface/java/DummyInterface.java b/tools/cooja/examples/userplatform_new_interface/java/DummyInterface.java new file mode 100644 index 000000000..24c71a636 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_interface/java/DummyInterface.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: DummyInterface.java,v 1.1 2006/08/21 12:13:13 fros4943 Exp $ + */ + +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * This is an example of how to implement new simulation interfaces. + * + * It needs read/write access to the following core variables: + *
    + *
  • char simDummyVar + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • dummy_interface + *
+ *

+ * This observable never changes. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Dummy Interface") +public class DummyInterface extends MoteInterface { + private static Logger logger = Logger.getLogger(DummyInterface.class); + + public DummyInterface(Mote mote) { + } + + public static String[] getCoreInterfaceDependencies() { + // I need the corresponding C dummy interface (in dummy_intf.c) + return new String[] { "dummy_interface" }; + } + + public void doActionsBeforeTick() { + logger.debug("Simulation (Java) dummy interface acts BEFORE mote tick"); + } + + public void doActionsAfterTick() { + logger.debug("Simulation (Java) dummy interface acts AFTER mote tick"); + } + + public JPanel getInterfaceVisualizer() { + return null; // No visualizer exists + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return 0.0; // I never require any energy + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/examples/userplatform_new_plugin/cooja.config b/tools/cooja/examples/userplatform_new_plugin/cooja.config new file mode 100644 index 000000000..c75e2409f --- /dev/null +++ b/tools/cooja/examples/userplatform_new_plugin/cooja.config @@ -0,0 +1 @@ +se.sics.cooja.GUI.PLUGINS = + MyDummyPlugin diff --git a/tools/cooja/examples/userplatform_new_plugin/java/MyDummyPlugin.java b/tools/cooja/examples/userplatform_new_plugin/java/MyDummyPlugin.java new file mode 100644 index 000000000..b0f90eff4 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_plugin/java/MyDummyPlugin.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MyDummyPlugin.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * This is an simple example plugin. + * It is a simulation plugin, which means that it depends on a single simulation. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Dummy Plugin") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class MyDummyPlugin extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(MyDummyPlugin.class); + private Simulation mySimulation; + private Observer tickObserver; + + /** + * Creates a new dummy plugin. + * + * @param simulationToVisualize Simulation to visualize + */ + public MyDummyPlugin(Simulation simulationToVisualize) { + super("This is my title!"); + mySimulation = simulationToVisualize; + + // Create and add a button + JButton button = new JButton("dummy button"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + logger.info("You just pressed my button"); + } + }); + add(button); + + // Register as tickobserver + mySimulation.addTickObserver(tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + logger.info("Another tick loop completed - simulation time is now:\t" + mySimulation.getSimulationTime()); + } + }); + + setSize(300,100); // Set an initial size of this plugin + + // Tries to select this plugin + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + mySimulation.deleteTickObserver(tickObserver); + } + +} diff --git a/tools/cooja/examples/userplatform_new_radiomedium/cooja.config b/tools/cooja/examples/userplatform_new_radiomedium/cooja.config new file mode 100644 index 000000000..9eb9aa4e8 --- /dev/null +++ b/tools/cooja/examples/userplatform_new_radiomedium/cooja.config @@ -0,0 +1 @@ +se.sics.cooja.GUI.RADIOMEDIUMS = + DummyRadioMedium diff --git a/tools/cooja/examples/userplatform_new_radiomedium/java/DummyRadioMedium.java b/tools/cooja/examples/userplatform_new_radiomedium/java/DummyRadioMedium.java new file mode 100644 index 000000000..bd16d69ca --- /dev/null +++ b/tools/cooja/examples/userplatform_new_radiomedium/java/DummyRadioMedium.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: DummyRadioMedium.java,v 1.1 2006/08/21 12:13:00 fros4943 Exp $ + */ + +import java.util.Collection; +import java.util.Observer; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; + +/** + * Dummy radio medium. No data is ever transferred through this medium. + * It also outputs some dummy debug messages during usage. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Dummy Radio Medium") +public class DummyRadioMedium extends RadioMedium { + private static Logger logger = Logger.getLogger(GUI.class); + + public void registerMote(Mote mote, Simulation sim) { + // Do nothing + logger.debug("I'm a dummy. Nothing will be registered by me."); + } + + public void unregisterMote(Mote mote, Simulation sim) { + // Do nothing + } + + public void registerRadioInterface(Radio radio, Position position, Simulation sim) { + // Do nothing + logger.debug("I'm a dummy. Nothing will be registered by me."); + } + + public void unregisterRadioInterface(Radio radio, Simulation sim) { + // Do nothing + } + + public void addRadioMediumObserver(Observer observer) { + // Do nothing + logger.debug("I'm a dummy. I will never change."); + } + + public void deleteRadioMediumObserver(Observer observer) { + // Do nothing + logger.debug("I'm a dummy. I will never change."); + } + + public RadioConnection[] getLastTickConnections() { + return null; + } + + public void setConnectionLogger(ConnectionLogger connection) { + // Do nothing + } + + public Collection getConfigXML() { + return null; + } + + public boolean setConfigXML(Collection configXML) { + return true; + } + +} diff --git a/tools/cooja/examples/userplatform_uaodv/cooja.config b/tools/cooja/examples/userplatform_uaodv/cooja.config new file mode 100644 index 000000000..21435db17 --- /dev/null +++ b/tools/cooja/examples/userplatform_uaodv/cooja.config @@ -0,0 +1,3 @@ +se.sics.cooja.GUI.PLUGINS = + VisUAODV UAODVControl +se.sics.cooja.contikimote.ContikiMoteType.C_SOURCES = + uaodv-example.c + diff --git a/tools/cooja/examples/userplatform_uaodv/java/UAODVControl.java b/tools/cooja/examples/userplatform_uaodv/java/UAODVControl.java new file mode 100644 index 000000000..ba9c7a2b6 --- /dev/null +++ b/tools/cooja/examples/userplatform_uaodv/java/UAODVControl.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: UAODVControl.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.interfaces.ContikiRS232; +import se.sics.cooja.interfaces.*; + +/** + * @author Fredrik Osterlind + */ +@ClassDescription("uAODV Control") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class UAODVControl extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(UAODVControl.class); + private Simulation mySimulation; + private JComboBox sourceComboBox; + private JComboBox destComboBox; + + /** + * @param simulationToVisualize Current simulation + */ + public UAODVControl(Simulation simulationToVisualize) { + super("uAODV Control (uses RS232)"); + mySimulation = simulationToVisualize; + + Container mainPane = this.getContentPane(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JLabel label; + JPanel smallPane; + + // Create available nodes list + Vector nodeDescs = new Vector(); + for (int i=0; i < mySimulation.getMotesCount(); i++) { + Mote currentMote = mySimulation.getMote(i); + nodeDescs.add("ID=" + + currentMote.getInterfaces().getMoteID().getMoteID() + + ", IP=" + + currentMote.getInterfaces().getIPAddress().getIPString()); + } + + // Create source combo box + label = new JLabel("Select RREQ source"); + + sourceComboBox = new JComboBox(nodeDescs); + if (sourceComboBox.getItemCount() < 1) { + logger.warn("No nodes available"); + } else + sourceComboBox.setSelectedIndex(0); + label.setLabelFor(sourceComboBox); + + smallPane = new JPanel(); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(sourceComboBox); + + mainPane.add(smallPane); + + // Create destination combo box + label = new JLabel("Select RREQ destination"); + + destComboBox = new JComboBox(nodeDescs); + if (destComboBox.getItemCount() < 1) { + logger.warn("No nodes available"); + } else + destComboBox.setSelectedIndex(0); + label.setLabelFor(destComboBox); + + smallPane = new JPanel(); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(destComboBox); + + mainPane.add(smallPane); + + // Add set button + smallPane = new JPanel(new BorderLayout()); + JButton setDestinationButton = new JButton("Set IP and send RREQ"); + setDestinationButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + logger.debug("Sending RS232 command now"); + Mote sourceMote = mySimulation.getMote(sourceComboBox.getSelectedIndex()); + Mote destMote = mySimulation.getMote(destComboBox.getSelectedIndex()); + if (sourceMote == null || destMote == null) { + logger.error("Error in mote selection"); + return; + } + + // Get destination IP + IPAddress destIP = destMote.getInterfaces().getIPAddress(); + if (destIP == null) { + logger.error("Error when fetching destination IP"); + return; + } + + // Set destination and start sending by using RS232 + ContikiRS232 rs232 = sourceMote.getInterfaces().getInterfaceOfType(ContikiRS232.class); + if (rs232 == null) { + logger.error("RS232 interface is null!"); + return; + } + + rs232.sendSerialMessage("SENDTO>" + destIP.getIPString()); + } + }); + smallPane.add(BorderLayout.EAST, setDestinationButton); + mainPane.add(smallPane); + + + pack(); + + // Tries to select this plugin + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + } + +} diff --git a/tools/cooja/examples/userplatform_uaodv/java/VisUAODV.java b/tools/cooja/examples/userplatform_uaodv/java/VisUAODV.java new file mode 100644 index 000000000..2e0c30ee0 --- /dev/null +++ b/tools/cooja/examples/userplatform_uaodv/java/VisUAODV.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisUAODV.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +import java.awt.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.Position; +import se.sics.cooja.plugins.*; + +/** + * VisUAODV is a 2D graphical visualizer for simulations with motes running + * UAODV protocol. + * RREQs are painted red, and RREPs green. + * The rest of sent data is painted black. + * + * Interactions with motes are available via registered mote plugins. + * + * @author Fredrik Osterlind + */ +@ClassDescription("uAODV Visualizer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class VisUAODV extends VisTraffic { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(VisUAODV.class); + + /** + * Creates a new VisUAODV visualizer. + * @param simulationToVisualize Simulation to visualize + */ + public VisUAODV(Simulation simulationToVisualize) { + super(simulationToVisualize); + setTitle("uAODV Visualizer"); + } + + protected void paintConnection(RadioConnection connection, Graphics g2d) { + Point sourcePixelPosition = transformPositionToPixel(connection.getSourcePosition()); + for (Position destPosition: connection.getDestinationPositons()) { + Point destPixelPosition = transformPositionToPixel(destPosition); + g2d.setColor(getColorOf(connection)); + + if (isRouteReply(connection.getSourceData())) { + ((Graphics2D) g2d).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f)); + } + + g2d.drawLine(sourcePixelPosition.x, sourcePixelPosition.y, + destPixelPosition.x, destPixelPosition.y); + + int hopCount = getHopCount(connection.getSourceData()); + if (hopCount >= 0) + g2d.drawString("" + hopCount, sourcePixelPosition.x, sourcePixelPosition.y); + } + } + + protected Color getColorOf(RadioConnection conn) { + if (isRouteRequest(conn.getSourceData())) + return Color.RED; + else if (isRouteReply(conn.getSourceData())) + return Color.GREEN; + else + return Color.BLACK; + } + + private boolean isRouteRequest(byte[] data) { + if (data.length > 28) + return data[28] == 1; + return false; + } + private boolean isRouteReply(byte[] data) { + if (data.length > 28) + return data[28] == 2; + return false; + } + private int getHopCount(byte[] data) { + if (data.length > 31) + return (int) data[31]; + return -1; + } + +} diff --git a/tools/cooja/examples/userplatform_uaodv/uaodv-example.c b/tools/cooja/examples/userplatform_uaodv/uaodv-example.c new file mode 100644 index 000000000..499af4dfb --- /dev/null +++ b/tools/cooja/examples/userplatform_uaodv/uaodv-example.c @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: uaodv-example.c,v 1.1 2006/08/21 12:13:12 fros4943 Exp $ + */ + +#include "contiki-net.h" +#include "net/uaodv.h" +#include "net/uaodv-rt.h" + +#include "lib/sensors.h" +#include "sys/log.h" + +#include "dev/button-sensor.h" +#include "dev/serial.h" + +/*---------------------------------------------------------------------------*/ +PROCESS(uaodv_example_process, "uAODV example"); + +AUTOSTART_PROCESSES(&uaodv_process, &uaodv_example_process); + +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(uaodv_example_process, ev, data) +{ + static uip_ipaddr_t addr; + + PROCESS_BEGIN(); + + int ipA, ipB, ipC, ipD; + char buf[200]; + + button_sensor.activate(); + serial_init(); + + while(1) { + PROCESS_WAIT_EVENT(); + if(ev == sensors_event && data == &button_sensor && button_sensor.value(0)) { + uip_ipaddr(addr, 10,10,0,1); + log_message("Sending RREQ to (static) 10.10.0.1\n", ""); + uaodv_request_route_to(addr); + } else if(ev == serial_event_message) { + sscanf(data, "SENDTO>%d.%d.%d.%d", &ipA, &ipB, &ipC, &ipD); + sprintf(buf, "Sending RREQ to %d.%d.%d.%d .. \n", ipA, ipB, ipC, ipD); + log_message(buf, ""); + uip_ipaddr(&addr, ipA, ipB, ipC, ipD); + uaodv_request_route_to(&addr); + } + } + + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ diff --git a/tools/cooja/java/se/sics/cooja/ClassDescription.java b/tools/cooja/java/se/sics/cooja/ClassDescription.java new file mode 100644 index 000000000..7203e00af --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/ClassDescription.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ClassDescription.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation type to describe a class. + * Description may be shown in menues etc. + * + * @author Fredrik Osterlind + */ +@Inherited +@Retention(RetentionPolicy.RUNTIME) +public @interface ClassDescription { + String value(); +} + diff --git a/tools/cooja/java/se/sics/cooja/ConnectionLogger.java b/tools/cooja/java/se/sics/cooja/ConnectionLogger.java new file mode 100644 index 000000000..236edf9ff --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/ConnectionLogger.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ConnectionLogger.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.io.File; +import java.io.FileOutputStream; +import org.apache.log4j.Logger; + +import se.sics.cooja.interfaces.Position; +import se.sics.cooja.plugins.SimControl; + +/** + * ConnectionLogger is a simple connection information outputter. All + * connections given via the method logConnection will be written to either + * default Log4J info stream, a log file or both. + * + * Log files have the following structure (spaces are tabs): SRC_POS [src_x] + * [src_y] [src_z] SRC_DATA [sent data bytes] DEST_POS [dest_x] [dest_y] + * [dest_z] DEST_DATA [received data bytes] [newline] + * + * @see RadioConnection + * @see RadioMedium + * + * @author Fredrik Osterlind + */ +public class ConnectionLogger { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(SimControl.class); + + private static int LOG_TO_FILE = 1; + private static int LOG_TO_LOG4J = 2; + private static int LOG_TO_FILE_AND_LOG4J = 3; + + private int myType; + private File myFile; + + /** + * Creates a new connection logger outputting to Log4J info stream. + */ + public ConnectionLogger() { + myType = LOG_TO_LOG4J; + } + + /** + * Creates a new connection logger outputting to a file. + * + * @param logFile + * Log file + */ + public ConnectionLogger(File logFile) { + myType = LOG_TO_FILE; + myFile = logFile; + if (myFile.exists()) + myFile.delete(); + } + + /** + * Output given connection to either Log4J info stream or a file. + * + * @param conn + * Connection to output + */ + public void logConnection(RadioConnection conn) { + + if (myType == LOG_TO_LOG4J || myType == LOG_TO_FILE_AND_LOG4J) { + if (conn.getDestinationPositons() != null + && conn.getDestinationPositons().length > 0) + for (Position destPos : conn.getDestinationPositons()) { + logger.info("RADIODATA from " + conn.getSourcePosition() + " to " + + destPos + "\tsize:" + conn.getSourceData().length); + } + else + logger.info("RADIODATA from " + conn.getSourcePosition() + " to " + + "[NOWHERE]" + "\tsize:" + conn.getSourceData().length); + } + if (myType == LOG_TO_FILE || myType == LOG_TO_FILE_AND_LOG4J) { + + try { + FileOutputStream out = new FileOutputStream(myFile, true); + + if (conn.getDestinationPositons() != null + && conn.getDestinationPositons().length > 0) { + for (int i = 0; i < conn.getDestinationPositons().length; i++) { + // Source pos + out.write("SRC_POS\t".getBytes()); + Position pos = conn.getSourcePosition(); + out.write(Double.toString(pos.getXCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getYCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getZCoordinate()).getBytes()); + out.write("\t".getBytes()); + + // Source data + out.write("SRC_DATA\t".getBytes()); + for (byte b : conn.getSourceData()) + out.write(Integer.toHexString((int) b).getBytes()); + out.write("\t".getBytes()); + + // Destination pos + out.write("DEST_POS\t".getBytes()); + pos = conn.getDestinationPositons()[i]; + out.write(Double.toString(pos.getXCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getYCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getZCoordinate()).getBytes()); + out.write("\t".getBytes()); + + // Source data + out.write("DEST_DATA\t".getBytes()); + for (byte b : conn.getDestinationData()[i]) + out.write(Integer.toHexString((int) b).getBytes()); + out.write("\t".getBytes()); + + out.write("\n".getBytes()); + } + + } else { + // Source pos + out.write("SRC_POS\t".getBytes()); + Position pos = conn.getSourcePosition(); + out.write(Double.toString(pos.getXCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getYCoordinate()).getBytes()); + out.write("\t".getBytes()); + out.write(Double.toString(pos.getZCoordinate()).getBytes()); + out.write("\t".getBytes()); + + // Source data + out.write("SRC_DATA\t".getBytes()); + for (byte b : conn.getSourceData()) + out.write(Integer.toHexString((int) b).getBytes()); + + out.write("\n".getBytes()); + } + out.close(); + + } catch (Exception e) { + logger.fatal("Exception while logging to file: " + e); + myType = LOG_TO_LOG4J; + return; + } + } + + } +} diff --git a/tools/cooja/java/se/sics/cooja/CoreComm.java b/tools/cooja/java/se/sics/cooja/CoreComm.java new file mode 100644 index 000000000..2b82cc0c7 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/CoreComm.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: CoreComm.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.io.File; +import se.sics.cooja.corecomm.*; + +/** + * The package corecomm's purpose is communicating with the simulation core + * using Java Native Interface (JNI). Each implementing class (named Lib[1-MAX]), + * loads a shared library which belongs to one mote type. The reason for this + * somewhat strange design is that once loaded, a native library cannot be unloaded + * in Java (yet). Therefore if we wish to load several libraries, the names and associated + * native functions must have unique names. And those names are defined via the calling class in JNI. + * For example, the native tick function in class Lib1 is named contiki_javasim_corecomm_Lib1_tick. + * When creating a new mote type, the main contiki source file is generated with function names + * compatible with the next available corecomm. + * This also implies that even if a mote type is deleted, a new one cannot be created without + * restarting the JVM and thus the entire simulation. + * + * Each implemented CoreComm class needs read access to the following core variables: + *

    + *
  • referenceVar + *
+ * and the following native functions: + *
    + *
  • init() + *
  • tick() + *
  • getReferenceAbsAddr() + *
  • getMemory(int start, int length) + *
  • setMemory(int start, int length, byte[] mem) + *
+ + * @author Fredrik Osterlind + */ +public abstract class CoreComm { + /** + * Maximum supported core communicators in a simulation. + */ + private final static int MAX_LIBRARIES = 8; + + // Static pointers to current libraries + private final static CoreComm[] coreComms = new CoreComm[MAX_LIBRARIES]; + private final static File[] coreCommFiles = new File[MAX_LIBRARIES]; + + /** + * Has any library been loaded? Since libraries can't be unloaded + * the entire simulator may have to be restarted. + * + * @return True if any library has been loaded this session + */ + public static boolean hasLibraryBeenLoaded() { + for (int i=0; i < coreComms.length; i++) + if (coreComms[i] != null) + return true; + return false; + } + + /** + * Has given library file already been loaded during this session? + * A loaded library can be removed, but not unloaded + * during one session. And a new library file, named + * the same as an earlier loaded and removed file, + * can't be loaded either. + * + * @param libraryFile Library file + * @return True if a library has already been loaded from the given file's filename + */ + public static boolean hasLibraryFileBeenLoaded(File libraryFile) { + for (File libFile: coreCommFiles) + if (libFile != null && libFile.getName().equals(libraryFile.getName())) + return true; + return false; + } + + /** + * Get the class name of next free core communicator class. + * If null is returned, no classes are available. + * + * @return Class name + */ + public static String getAvailableClassName() { + if (coreComms[0] == null) + return "Lib1"; + if (coreComms[1] == null) + return "Lib2"; + if (coreComms[2] == null) + return "Lib3"; + if (coreComms[3] == null) + return "Lib4"; + if (coreComms[4] == null) + return "Lib5"; + if (coreComms[5] == null) + return "Lib6"; + if (coreComms[6] == null) + return "Lib7"; + if (coreComms[7] == null) + return "Lib8"; + + return null; + } + + /** + * Create and return an instance of the core communicator identified + * by className. This core communicator will load the native library libFile. + * + * @param className Class name of core communicator + * @param libFile Native library file + * @return Core Communicator + */ + public static CoreComm createCoreComm(String className, File libFile) { + if (className.equals("Lib1") && coreComms[0] == null) { + coreComms[0] = new Lib1(libFile); + coreCommFiles[0] = libFile; + return coreComms[0]; + } + if (className.equals("Lib2") && coreComms[1] == null) { + coreComms[1] = new Lib2(libFile); + coreCommFiles[1] = libFile; + return coreComms[1]; + } + if (className.equals("Lib3") && coreComms[2] == null) { + coreComms[2] = new Lib3(libFile); + coreCommFiles[2] = libFile; + return coreComms[2]; + } + if (className.equals("Lib4") && coreComms[3] == null) { + coreComms[3] = new Lib4(libFile); + coreCommFiles[3] = libFile; + return coreComms[3]; + } + if (className.equals("Lib5") && coreComms[4] == null) { + coreComms[4] = new Lib5(libFile); + coreCommFiles[4] = libFile; + return coreComms[4]; + } + if (className.equals("Lib6") && coreComms[5] == null) { + coreComms[5] = new Lib6(libFile); + coreCommFiles[5] = libFile; + return coreComms[5]; + } + if (className.equals("Lib7") && coreComms[6] == null) { + coreComms[6] = new Lib7(libFile); + coreCommFiles[6] = libFile; + return coreComms[6]; + } + if (className.equals("Lib8") && coreComms[7] == null) { + coreComms[7] = new Lib8(libFile); + coreCommFiles[7] = libFile; + return coreComms[7]; + } + + return null; + } + + + /** + * Ticks a mote once. This should not be used directly, + * but instead via Mote.tick(). + */ + public abstract void tick(); + + /** + * Initializes a mote by running a startup script in the core. + * (Should only by run once, at the same time as the library is loaded) + */ + protected abstract void init(); + + /** + * Returns absolute memory location of the core variable + * referenceVar. Used to get offset between relative and absolute + * memory addresses. + * + * @return Absolute memory address + */ + public abstract int getReferenceAbsAddr(); + + /** + * Returns a memory segment identified by start and length. + * + * @param start Start address of segment + * @param length Length of segment + * @return Memory segment + */ + public abstract byte[] getMemory(int start, int length); + + /** + * Overwrites a memory segment identified by start and length. + * + * @param start Start address of segment + * @param length Length of segment + * @param mem Data to fill memory segment + */ + public abstract void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/DirectoryClassLoader.java b/tools/cooja/java/se/sics/cooja/DirectoryClassLoader.java new file mode 100644 index 000000000..f9e5bbc10 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/DirectoryClassLoader.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: DirectoryClassLoader.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.io.*; +import org.apache.log4j.Logger; + +/** + * Loads an external file from the given directory as a Java class. + * + * @author Fredrik Osterlind + */ +public class DirectoryClassLoader extends ClassLoader { + private static Logger logger = Logger.getLogger(DirectoryClassLoader.class); + private File directory; + + /** + * Creates a new class loader reading from given directory. + * + * @param directory + * Directory + */ + public DirectoryClassLoader(File directory) { + super(); + this.directory = directory; + } + + /** + * Creates a new class loader reading from given directory, with the given + * class loader as parent class loader. + * + * @param parent + * Parent class loader + * @param directory + * Directory + */ + public DirectoryClassLoader(ClassLoader parent, File directory) { + super(parent); + this.directory = directory; + } + + public Class findClass(String name) throws ClassNotFoundException { + String fullFilePath = directory.getPath() + File.separatorChar + name + + ".class"; + + // Read external file + //logger.info("Directory class loader reading file: " + fullFilePath); + byte[] classData = loadClassData(fullFilePath); + if (classData == null) { + throw new ClassNotFoundException(); + } + + // Create class + return defineClass(name, classData, 0, classData.length); + } + + private byte[] loadClassData(String name) { + // Support for fill class names in configuration file + // TODO Quick-fix (may contain bugs) + name = name.replace('.', File.separatorChar); + name = name.replace(File.separatorChar + "class", ".class"); + + // Open file for read access + File classFile = new File(name); + InputStream inputStream = null; + if (!classFile.exists()) { + //logger.fatal("File " + classFile + " does not exist!"); + return null; + } + + try { + inputStream = new FileInputStream(classFile); + + if (inputStream == null) { + logger.fatal("File input stream is null!"); + return null; + } + } catch (FileNotFoundException e) { + logger.fatal("Could not open file (not found?)!"); + return null; + } + long fileSize = classFile.length(); + + if (fileSize > Integer.MAX_VALUE) { + logger.fatal("Class file is too large"); + return null; + } + + // Read class data + byte[] classData = new byte[(int) fileSize]; + int offset = 0; + int numRead = 0; + try { + while (offset < classData.length + && (numRead = inputStream.read(classData, offset, classData.length + - offset)) >= 0) { + offset += numRead; + } + + inputStream.close(); + } catch (IOException e) { + logger.fatal("Error when reading class file"); + return null; + } + + // Ensure all the bytes have been read in + if (offset < classData.length) { + logger.fatal("Could not read entire class file"); + return null; + } + + return classData; + } +} diff --git a/tools/cooja/java/se/sics/cooja/GUI.java b/tools/cooja/java/se/sics/cooja/GUI.java new file mode 100644 index 000000000..96e41f269 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/GUI.java @@ -0,0 +1,1612 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: GUI.java,v 1.1 2006/08/21 12:12:51 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import javax.swing.*; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; +import javax.swing.filechooser.FileFilter; +import org.apache.log4j.Logger; +import org.apache.log4j.xml.DOMConfigurator; + +import se.sics.cooja.dialogs.*; +import se.sics.cooja.plugins.*; + +/** + * Main file of COOJA Simulator. + * + * @author Fredrik Osterlind + */ +public class GUI extends JDesktopPane { + + /** + * External tools default Win32 settings filename. + */ + public static final String EXTERNAL_TOOLS_WIN32_SETTINGS_FILENAME = "/external_tools_win32.config"; + + /** + * External tools default Linux/Unix settings filename. + */ + public static final String EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME = "/external_tools_linux.config"; + + /** + * External tools user settings filename. + */ + public static final String EXTERNAL_TOOLS_USER_SETTINGS_FILENAME = "external_tools_user.config"; + + /** + * Logger settings filename. + */ + public static final String LOG_CONFIG_FILE = "log4j_config.xml"; + + /** + * Default platform configuration filename. + */ + public static final String PLATFORM_DEFAULT_CONFIG_FILENAME = "cooja_default.config"; + + /** + * User platform configuration filename. + */ + public static final String PLATFORM_CONFIG_FILENAME = "cooja.config"; + + /** + * File filter only showing saved simulations files (*.csc). + */ + public static final FileFilter SAVED_SIMULATIONS_FILES = new FileFilter() { + public boolean accept(File file) { + if (file.isDirectory()) + return true; + + if (file.getName().endsWith(".csc")) + return true; + + return false; + } + + public String getDescription() { + return "COOJA Configuration files"; + } + + public String toString() { + return ".csc"; + } + }; + + /** + * Main frame for current GUI + */ + public static JFrame frame; + + /** + * Current active simulation + */ + public static Simulation currentSimulation = null; + + /** + * Current active GUI + */ + public static GUI currentGUI = null; + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(GUI.class); + + // External tools setting names + private static Properties currentExternalToolsSettings; + private static final String externalToolsSettingNames[] = new String[]{ + "PATH_CONTIKI", "PATH_COOJA_CORE_RELATIVE", "PATH_MAKE", "PATH_SHELL", + "PATH_C_COMPILER", "COMPILER_ARGS", "PATH_LINKER", "LINKER_ARGS_1", + "LINKER_ARGS_2", "CONTIKI_STANDARD_PROCESSES", "CMD_GREP_PROCESSES", + "REGEXP_PARSE_PROCESSES", "CMD_GREP_INTERFACES", + "REGEXP_PARSE_INTERFACES", "CMD_GREP_SENSORS", "REGEXP_PARSE_SENSORS", + "CONTIKI_MAIN_TEMPLATE_FILENAME"}; + + private static final int FRAME_NEW_OFFSET = 30; + private static final int FRAME_STANDARD_WIDTH = 150; + private static final int FRAME_STANDARD_HEIGHT = 300; + + private GUI myGUI; + private Mote selectedMote = null; + private GUIEventHandler guiEventHandler = new GUIEventHandler(); + + private JMenu menuPlugins, menuMoteTypeClasses, menuMoteTypes; + private JPopupMenu menuMotePlugins; + + // Platform configuration variables + // Maintained via method reparsePlatformConfig() + private PlatformConfig platformConfig; + private Vector currentUserPlatforms = new Vector(); + private ClassLoader userPlatformClassLoader; + + private Vector> moteTypeClasses = new Vector>(); + private Vector> pluginClasses = new Vector>(); + private Vector> pluginClassesTemporary = new Vector>(); + private Vector> radioMediumClasses = new Vector>(); + private Vector> ipDistributorClasses = new Vector>(); + private Vector> positionerClasses = new Vector>(); + + /** + * Creates a new COOJA Simulator GUI. + */ + public GUI() { + myGUI = this; + currentGUI = this; + + // Set drag frames to outlines only (faster) + setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); + + // Add menu bar + frame.setJMenuBar(createMenuBar()); + + frame.setSize(700, 700); + + frame.addWindowListener(guiEventHandler); + + // Load default and overwrite with user settings (if any) + loadExternalToolsDefaultSettings(); + loadExternalToolsUserSettings(); + + // If simulator was quick-started (via make), double-check that the + // contiki-paths are equal + if (System.getProperty("PATH_CONTIKI") != null) { + String makefilePath = null; + String userSettingsPath = null; + try { + makefilePath = new File(System.getProperty("PATH_CONTIKI")) + .getCanonicalPath(); + userSettingsPath = new File(getExternalToolsSetting("PATH_CONTIKI")) + .getCanonicalPath(); + } catch (Exception e) { + } + + if (makefilePath == null || userSettingsPath == null + || !makefilePath.equals(userSettingsPath)) { + // Show dialog asking user which path to use + Object[] options = {"Use new temporarily", "Keep old (unsafe)"}; + String question = "COOJA has detected a possible error in your current Contiki 2.x path!\n" + + "Maybe COOJA was started via a make script which defined a newer path.\n" + + "Old user settings: " + + getExternalToolsSetting("PATH_CONTIKI") + + "\n" + + "New makefile path: " + + System.getProperty("PATH_CONTIKI") + + "\n"; + + String title = "Possible error in Contiki 2.x path"; + + int answer = JOptionPane.showOptionDialog(this, question, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, + options, options[0]); + + if (answer == JOptionPane.YES_OPTION) { + setExternalToolsSetting("PATH_CONTIKI", System + .getProperty("PATH_CONTIKI")); + logger.info("New Contiki path settings are not saved"); + } + } + } + + // Load extendable parts (using current platform config) + reparsePlatformConfig(); + } + + private JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + JMenu menu; + JMenuItem menuItem; + + // File menu + menu = new JMenu("File"); + menu.setMnemonic(KeyEvent.VK_F); + menuBar.add(menu); + + menuItem = new JMenuItem("New simulation"); + menuItem.setMnemonic(KeyEvent.VK_N); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, + ActionEvent.CTRL_MASK)); + menuItem.setActionCommand("new sim"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menuItem = new JMenuItem("Close simulation"); + menuItem.setMnemonic(KeyEvent.VK_C); + menuItem.setActionCommand("close sim"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menuItem = new JMenuItem("Load simulation"); + menuItem.setMnemonic(KeyEvent.VK_L); + menuItem.setActionCommand("load sim"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menuItem = new JMenuItem("Save simulation"); + menuItem.setMnemonic(KeyEvent.VK_S); + menuItem.setActionCommand("save sim"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menu.addSeparator(); + + menuItem = new JMenuItem("Quit"); + menuItem.setMnemonic(KeyEvent.VK_Q); + menuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, + ActionEvent.CTRL_MASK)); + menuItem.setActionCommand("quit"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + // Simulation menu + menu = new JMenu("Simulation"); + menu.setMnemonic(KeyEvent.VK_S); + menuBar.add(menu); + + menuItem = new JMenuItem("Open Control"); + menuItem.setMnemonic(KeyEvent.VK_C); + menuItem.setActionCommand("start plugin"); + menuItem.putClientProperty("class", SimControl.class); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menuItem = new JMenuItem("Information"); + menuItem.setMnemonic(KeyEvent.VK_I); + menuItem.setActionCommand("start plugin"); + menuItem.putClientProperty("class", SimInformation.class); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + // Mote type menu + menu = new JMenu("Mote Types"); + menu.setMnemonic(KeyEvent.VK_T); + menuBar.add(menu); + + // Mote type classes sub menu + menuMoteTypeClasses = new JMenu("Create mote type"); + menuMoteTypeClasses.setMnemonic(KeyEvent.VK_C); + menuMoteTypeClasses.addMenuListener(new MenuListener() { + public void menuSelected(MenuEvent e) { + // Clear menu + menuMoteTypeClasses.removeAll(); + + // Recreate menu items + JMenuItem menuItem; + + for (Class moteTypeClass : moteTypeClasses) { + String description = GUI.getDescriptionOf(moteTypeClass); + menuItem = new JMenuItem(description); + menuItem.setActionCommand("create mote type"); + menuItem.putClientProperty("class", moteTypeClass); + menuItem.addActionListener(guiEventHandler); + menuMoteTypeClasses.add(menuItem); + } + } + public void menuDeselected(MenuEvent e) { + } + public void menuCanceled(MenuEvent e) { + } + }); + menu.add(menuMoteTypeClasses); + + menuItem = new JMenuItem("Information"); + menuItem.setActionCommand("start plugin"); + menuItem.putClientProperty("class", MoteTypeInformation.class); + menuItem.addActionListener(guiEventHandler); + + menu.add(menuItem); + + // Mote menu + menu = new JMenu("Motes"); + menu.setMnemonic(KeyEvent.VK_M); + menuBar.add(menu); + + // Mote types sub menu + menuMoteTypes = new JMenu("Add motes of type"); + menuMoteTypes.setMnemonic(KeyEvent.VK_A); + menuMoteTypes.addMenuListener(new MenuListener() { + public void menuSelected(MenuEvent e) { + // Clear menu + menuMoteTypes.removeAll(); + + if (currentSimulation == null) { + return; + } + + // Recreate menu items + JMenuItem menuItem; + + for (MoteType moteType : currentSimulation.getMoteTypes()) { + menuItem = new JMenuItem(moteType.getDescription()); + menuItem.setActionCommand("add motes"); + menuItem.setToolTipText(getDescriptionOf(moteType.getClass())); + menuItem.putClientProperty("motetype", moteType); + menuItem.addActionListener(guiEventHandler); + menuMoteTypes.add(menuItem); + } + } + public void menuDeselected(MenuEvent e) { + } + public void menuCanceled(MenuEvent e) { + } + }); + menu.add(menuMoteTypes); + + // Plugins menu + menuPlugins = new JMenu("Plugins"); + menuPlugins.setMnemonic(KeyEvent.VK_P); + menuBar.add(menuPlugins); + + // Settings menu + menu = new JMenu("Settings"); + menuBar.add(menu); + + menuItem = new JMenuItem("External tools paths"); + menuItem.setActionCommand("edit paths"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + menuItem = new JMenuItem("Manage user platforms"); + menuItem.setActionCommand("manage platforms"); + menuItem.addActionListener(guiEventHandler); + menu.add(menuItem); + + // Mote plugins popup menu (not available via menu bar) + menuMotePlugins = new JPopupMenu(); + menuMotePlugins.add(new JLabel("Open mote plugin:")); + menuMotePlugins.add(new JSeparator()); + + return menuBar; + } + + private static void createAndShowGUI() { + + // Make sure we have nice window decorations. + JFrame.setDefaultLookAndFeelDecorated(true); + JDialog.setDefaultLookAndFeelDecorated(true); + + // Create and set up the window. + frame = new JFrame("COOJA Simulator"); + frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + + // Create and set up the content pane. + JComponent newContentPane = new GUI(); + newContentPane.setOpaque(true); + frame.setContentPane(newContentPane); + frame.setLocationRelativeTo(null); + + // Display the window. + frame.setVisible(true); + } + + // // PLATFORM CONFIG AND EXTENDABLE PARTS METHODS //// + + /** + * Register new mote type class. + * + * @param moteTypeClass + * Class to register + */ + public void registerMoteType(Class moteTypeClass) { + moteTypeClasses.add(moteTypeClass); + } + + /** + * Unregister all mote type classes. + */ + public void unregisterMoteTypes() { + moteTypeClasses.clear(); + } + + /** + * @return All registered mote type classes + */ + public Vector> getRegisteredMoteTypes() { + return moteTypeClasses; + } + + /** + * Register new IP distributor class + * + * @param ipDistributorClass + * Class to register + * @return True if class was registered + */ + public boolean registerIPDistributor( + Class ipDistributorClass) { + // Check that vector constructor exists + try { + ipDistributorClass.getConstructor(new Class[]{Vector.class}); + } catch (Exception e) { + logger.fatal("No vector constructor found of IP distributor: " + + ipDistributorClass); + return false; + } + + ipDistributorClasses.add(ipDistributorClass); + return true; + } + + /** + * Unregister all IP distributors. + */ + public void unregisterIPDistributors() { + ipDistributorClasses.clear(); + } + + /** + * @return All registered IP distributors + */ + public Vector> getRegisteredIPDistributors() { + return ipDistributorClasses; + } + + /** + * Register new positioner class. + * + * @param positionerClass + * Class to register + * @return True if class was registered + */ + public boolean registerPositioner(Class positionerClass) { + // Check that interval constructor exists + try { + positionerClass + .getConstructor(new Class[]{int.class, double.class, double.class, + double.class, double.class, double.class, double.class}); + } catch (Exception e) { + logger.fatal("No interval constructor found of positioner: " + + positionerClass); + return false; + } + + positionerClasses.add(positionerClass); + return true; + } + + /** + * Unregister all positioner classes. + */ + public void unregisterPositioners() { + positionerClasses.clear(); + } + + /** + * @return All registered positioner classes + */ + public Vector> getRegisteredPositioners() { + return positionerClasses; + } + + /** + * Register new radio medium class. + * + * @param radioMediumClass + * Class to register + * @return True if class was registered + */ + public boolean registerRadioMedium( + Class radioMediumClass) { + radioMediumClasses.add(radioMediumClass); + return true; + } + + /** + * Unregister all radio medium classes. + */ + public void unregisterRadioMediums() { + radioMediumClasses.clear(); + } + + /** + * @return All registered radio medium classes + */ + public Vector> getRegisteredRadioMediums() { + return radioMediumClasses; + } + + /** + * Builds new platform configuration using current user platforms settings. + * Reregisters mote types, plugins, IP distributors, positioners and radio + * mediums. This method may still return true even if all classes could not be + * registered, but always returns false if all user platform configuration + * files were not parsed correctly. + * + * Any registered temporary plugins will be saved and reregistered. + * + * @return True if external configuration files were found and parsed OK + */ + public boolean reparsePlatformConfig() { + // Backup temporary plugins + Vector> oldTempPlugins = (Vector>) pluginClassesTemporary + .clone(); + + // Reset current configuration + unregisterMoteTypes(); + unregisterPlugins(); + unregisterIPDistributors(); + unregisterPositioners(); + unregisterRadioMediums(); + + // Read default configuration + platformConfig = new PlatformConfig(); + // logger.info("Loading default platform configuration: " + + // PLATFORM_DEFAULT_CONFIG_FILENAME); + try { + platformConfig.appendConfig(new File(PLATFORM_DEFAULT_CONFIG_FILENAME)); + } catch (FileNotFoundException e) { + logger.fatal("Could not find default platform config file: " + + PLATFORM_DEFAULT_CONFIG_FILENAME); + return false; + } catch (IOException e) { + logger.fatal("Error when reading default platform config file: " + + PLATFORM_DEFAULT_CONFIG_FILENAME); + return false; + } + + // Append user platform configurations + for (File userPlatform : currentUserPlatforms) { + File userPlatformConfig = new File(userPlatform.getPath() + + File.separatorChar + PLATFORM_CONFIG_FILENAME); + // logger.info("Loading platform configuration: " + userPlatformConfig); + + try { + // Append config to general config + platformConfig.appendConfig(userPlatformConfig); + } catch (FileNotFoundException e) { + logger.fatal("Could not find platform config file: " + + userPlatformConfig); + return false; + } catch (IOException e) { + logger.fatal("Error when reading platform config file: " + + userPlatformConfig); + return false; + } + } + + // Create class loader + userPlatformClassLoader = createClassLoader(currentUserPlatforms); + + // Register mote types + String[] moteTypeClassNames = platformConfig.getStringArrayValue(GUI.class, + "MOTETYPES"); + if (moteTypeClassNames != null) { + for (String moteTypeClassName : moteTypeClassNames) { + Class moteTypeClass = tryLoadClass(this, + MoteType.class, moteTypeClassName); + + if (moteTypeClass != null) { + registerMoteType(moteTypeClass); + // logger.info("Loaded mote type class: " + moteTypeClassName); + } else { + logger.warn("Could not load mote type class: " + moteTypeClassName); + } + } + } + + // Register plugins + registerPlugin(SimControl.class, false); // Not in menu + registerPlugin(SimInformation.class, false); // Not in menu + registerPlugin(MoteTypeInformation.class, false); // Not in menu + String[] pluginClassNames = platformConfig.getStringArrayValue(GUI.class, + "PLUGINS"); + if (pluginClassNames != null) { + for (String pluginClassName : pluginClassNames) { + Class pluginClass = tryLoadClass(this, + VisPlugin.class, pluginClassName); + + if (pluginClass != null) { + registerPlugin(pluginClass); + // logger.info("Loaded plugin class: " + pluginClassName); + } else { + logger.warn("Could not load plugin class: " + pluginClassName); + } + } + } + + // Reregister temporary plugins again + if (oldTempPlugins != null) { + for (Class pluginClass : oldTempPlugins) { + if (registerTemporaryPlugin(pluginClass)) { + // logger.info("Reregistered temporary plugin class: " + + // getDescriptionOf(pluginClass)); + } else + logger.warn("Could not reregister temporary plugin class: " + + getDescriptionOf(pluginClass)); + } + } + + // Register IP distributors + String[] ipDistClassNames = platformConfig.getStringArrayValue(GUI.class, + "IP_DISTRIBUTORS"); + if (ipDistClassNames != null) { + for (String ipDistClassName : ipDistClassNames) { + Class ipDistClass = tryLoadClass(this, + IPDistributor.class, ipDistClassName); + + if (ipDistClass != null) { + registerIPDistributor(ipDistClass); + // logger.info("Loaded IP distributor class: " + ipDistClassName); + } else { + logger + .warn("Could not load IP distributor class: " + ipDistClassName); + } + } + } + + // Register positioners + String[] positionerClassNames = platformConfig.getStringArrayValue( + GUI.class, "POSITIONERS"); + if (positionerClassNames != null) { + for (String positionerClassName : positionerClassNames) { + Class positionerClass = tryLoadClass(this, + Positioner.class, positionerClassName); + + if (positionerClass != null) { + registerPositioner(positionerClass); + // logger.info("Loaded positioner class: " + positionerClassName); + } else { + logger + .warn("Could not load positioner class: " + positionerClassName); + } + } + } + + // Register radio mediums + String[] radioMediumsClassNames = platformConfig.getStringArrayValue( + GUI.class, "RADIOMEDIUMS"); + if (radioMediumsClassNames != null) { + for (String radioMediumClassName : radioMediumsClassNames) { + Class radioMediumClass = tryLoadClass(this, + RadioMedium.class, radioMediumClassName); + + if (radioMediumClass != null) { + registerRadioMedium(radioMediumClass); + // logger.info("Loaded radio medium class: " + radioMediumClassName); + } else { + logger.warn("Could not load radio medium class: " + + radioMediumClassName); + } + } + } + + return true; + } + + /** + * Returns the current platform configuration common to the entire simulator. + * + * @return Current platform configuration + */ + public PlatformConfig getPlatformConfig() { + return platformConfig; + } + + /** + * Returns the current user platforms common to the entire simulator. + * + * @return Current user platforms. + */ + public Vector getUserPlatforms() { + return currentUserPlatforms; + } + + // // PLUGIN METHODS //// + + /** + * Show a started plugin in working area. + * + * @param plugin + * Internal frame to add + */ + public void showPlugin(VisPlugin plugin) { + int nrFrames = this.getAllFrames().length; + add(plugin); + + // Set standard size if not specified by plugin itself + if (plugin.getWidth() <= 0 || plugin.getHeight() <= 0) { + plugin.setSize(FRAME_STANDARD_WIDTH, FRAME_STANDARD_HEIGHT); + } + + // Set location if not already visible + if (!plugin.isVisible()) { + plugin.setLocation((nrFrames + 1) * FRAME_NEW_OFFSET, (nrFrames + 1) + * FRAME_NEW_OFFSET); + plugin.setVisible(true); + } + + // Mote to front and select plugin + myGUI.moveToFront(plugin); + myGUI.setSelectedFrame(plugin); + } + + /** + * Remove a plugin from working area. + * + * @param plugin + * Plugin to remove + * @param askUser + * If plugin is the last one, ask user if we should remove current + * simulation also? + */ + public void removePlugin(VisPlugin plugin, boolean askUser) { + // Clear any allocated resources and remove plugin + plugin.closePlugin(); + plugin.dispose(); + + if (askUser && myGUI.getAllFrames().length == 0) { + String s1 = "Remove"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane.showOptionDialog(frame, + "You have an active simulation.\nDo you want to remove it?", + "Remove current simulation?", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) { + return; + } + doRemoveSimulation(false); + } + } + + /** + * Starts a plugin of given plugin class. If the plugin is a mote plugin the + * currently selected mote will be given as an argument. + * + * @param pluginClass + * Plugin class + * @return True if plugin was started, false otherwise + */ + protected boolean startPlugin(Class pluginClass) { + // Check that plugin class is registered + if (!pluginClasses.contains(pluginClass)) { + logger.fatal("Plugin class not registered: " + pluginClass); + return false; + } + + // Instantiate and show plugin differently depending on plugin type + VisPlugin newPlugin = null; + int pluginType = pluginClass.getAnnotation(VisPluginType.class).value(); + + try { + if (pluginType == VisPluginType.MOTE_PLUGIN) { + if (selectedMote == null) { + logger.fatal("Can't start mote plugin (no mote selected)"); + return false; + } + + newPlugin = pluginClass.getConstructor(new Class[]{Mote.class}) + .newInstance(selectedMote); + selectedMote = null; + } else if (pluginType == VisPluginType.SIM_PLUGIN) { + if (currentSimulation == null) { + logger.fatal("Can't start simulation plugin (no simulation)"); + return false; + } + + newPlugin = pluginClass.getConstructor(new Class[]{Simulation.class}) + .newInstance(currentSimulation); + } else if (pluginType == VisPluginType.SIM_STANDARD_PLUGIN) { + if (currentSimulation == null) { + logger + .fatal("Can't start simulation standard plugin (no simulation)"); + return false; + } + + newPlugin = pluginClass.getConstructor(new Class[]{Simulation.class}) + .newInstance(currentSimulation); + } else if (pluginType == VisPluginType.GUI_PLUGIN) { + if (currentGUI == null) { + logger.fatal("Can't start GUI plugin (no GUI)"); + return false; + } + + newPlugin = pluginClass.getConstructor(new Class[]{GUI.class}) + .newInstance(currentGUI); + } + } catch (Exception e) { + logger.fatal("Exception thrown when starting plugin: " + e); + return false; + } + + if (newPlugin == null) + return false; + + // Show plugin + myGUI.showPlugin(newPlugin); + return true; + } + + /** + * Register a plugin to be included in the GUI. The plugin will be visible in + * the menubar. + * + * @param newPluginClass + * New plugin to register + * @return True if this plugin was registered ok, false otherwise + */ + public boolean registerPlugin(Class newPluginClass) { + return registerPlugin(newPluginClass, true); + } + + /** + * Register a temporary plugin to be included in the GUI. The plugin will be + * visible in the menubar. This plugin will automatically be unregistered if + * the current simulation is removed. + * + * @param newPluginClass + * New plugin to register + * @return True if this plugin was registered ok, false otherwise + */ + public boolean registerTemporaryPlugin( + Class newPluginClass) { + if (pluginClasses.contains(newPluginClass)) + return false; + + boolean returnVal = registerPlugin(newPluginClass, true); + if (!returnVal) + return false; + + pluginClassesTemporary.add(newPluginClass); + return true; + } + + /** + * Unregister a plugin class. Removes any plugin menu items links as well. + * + * @param pluginClass + * Plugin class to unregister + */ + public void unregisterPlugin(Class pluginClass) { + + // Remove (if existing) plugin class menu items + for (Component menuComponent : menuPlugins.getMenuComponents()) { + if (menuComponent.getClass().isAssignableFrom(JMenuItem.class)) { + JMenuItem menuItem = (JMenuItem) menuComponent; + if (menuItem.getClientProperty("class").equals(pluginClass)) + menuPlugins.remove(menuItem); + } + } + for (MenuElement menuComponent : menuMotePlugins.getSubElements()) { + if (menuComponent.getClass().isAssignableFrom(JMenuItem.class)) { + JMenuItem menuItem = (JMenuItem) menuComponent; + if (menuItem.getClientProperty("class").equals(pluginClass)) + menuPlugins.remove(menuItem); + } + } + + // Remove from plugin vectors (including temporary) + if (pluginClasses.contains(pluginClass)) + pluginClasses.remove(pluginClass); + if (pluginClassesTemporary.contains(pluginClass)) + pluginClassesTemporary.remove(pluginClass); + } + + /** + * Register a plugin to be included in the GUI. + * + * @param newPluginClass + * New plugin to register + * @param addToMenu + * Should this plugin be added to the dedicated plugins menubar? + * @return True if this plugin was registered ok, false otherwise + */ + private boolean registerPlugin(Class newPluginClass, + boolean addToMenu) { + + // Get description annotation (if any) + String description = getDescriptionOf(newPluginClass); + + // Get plugin type annotation (required) + int pluginType; + if (newPluginClass.isAnnotationPresent(VisPluginType.class)) { + pluginType = newPluginClass.getAnnotation(VisPluginType.class).value(); + } else { + pluginType = VisPluginType.UNDEFINED_PLUGIN; + } + + // Check that plugin type is valid and constructor exists + try { + if (pluginType == VisPluginType.MOTE_PLUGIN) { + newPluginClass.getConstructor(new Class[]{Mote.class}); + } else if (pluginType == VisPluginType.SIM_PLUGIN) { + newPluginClass.getConstructor(new Class[]{Simulation.class}); + } else if (pluginType == VisPluginType.SIM_STANDARD_PLUGIN) { + newPluginClass.getConstructor(new Class[]{Simulation.class}); + } else if (pluginType == VisPluginType.GUI_PLUGIN) { + newPluginClass.getConstructor(new Class[]{GUI.class}); + } else { + logger.fatal("Could not find valid plugin type annotation in class " + + newPluginClass); + return false; + } + } catch (NoSuchMethodException e) { + logger.fatal("Could not find valid constructor in class " + + newPluginClass + ": " + e); + return false; + } + + if (addToMenu) { + // Create 'start plugin'-menu item + JMenuItem menuItem = new JMenuItem(description); + menuItem.setActionCommand("start plugin"); + menuItem.putClientProperty("class", newPluginClass); + menuItem.addActionListener(guiEventHandler); + + menuPlugins.add(menuItem); + + if (pluginType == VisPluginType.MOTE_PLUGIN) { + // Disable previous menu item and add new item to mote plugins menu + menuItem.setEnabled(false); + menuItem.setToolTipText("Mote plugin"); + + menuItem = new JMenuItem(description); + menuItem.setActionCommand("start plugin"); + menuItem.putClientProperty("class", newPluginClass); + menuItem.addActionListener(guiEventHandler); + menuMotePlugins.add(menuItem); + } + } + + pluginClasses.add(newPluginClass); + return true; + } + + /** + * Unregister all plugin classes, including temporary plugins. + */ + public void unregisterPlugins() { + menuPlugins.removeAll(); + menuMotePlugins.removeAll(); + pluginClasses.clear(); + pluginClassesTemporary.clear(); + } + + /** + * Show mote plugins menu for starting a mote plugin. All registered mote + * plugins can be selected from. + * + * @param invoker + * Component that wants to display the menu + * @param mote + * Mote of plugin selected + * @param location + * Location of popup menu + */ + public void showMotePluginsMenu(Component invoker, Mote mote, Point location) { + menuMotePlugins.setInvoker(invoker); + menuMotePlugins.setLocation(location); + menuMotePlugins.setVisible(true); + selectedMote = mote; + } + + // // GUI CONTROL METHODS //// + + private void setSimulation(Simulation sim) { + if (sim != null) { + doRemoveSimulation(false); + } + currentSimulation = sim; + + // Set frame title + frame.setTitle("COOJA Simulator" + " - " + sim.getTitle()); + + // Open standard plugins + for (Class visPluginClass : pluginClasses) { + int pluginType = visPluginClass.getAnnotation(VisPluginType.class) + .value(); + if (pluginType == VisPluginType.SIM_STANDARD_PLUGIN) { + startPlugin(visPluginClass); + } + } + } + + /** + * Creates a new mote type of the given mote type class. + * + * @param moteTypeClass + * Mote type class + */ + public void doCreateMoteType(Class moteTypeClass) { + if (currentSimulation == null) { + logger.fatal("Can't create mote type (no simulation)"); + return; + } + + // Stop simulation (if running) + currentSimulation.stopSimulation(); + + // Create mote type + MoteType newMoteType = null; + boolean moteTypeOK = false; + try { + newMoteType = moteTypeClass.newInstance(); + moteTypeOK = newMoteType.configureAndInit(frame, currentSimulation); + } catch (Exception e) { + logger.fatal("Exception when creating mote type: " + e); + return; + } + + // Add mote type to simulation + if (newMoteType != null && moteTypeOK) { + currentSimulation.addMoteType(newMoteType); + } + } + + /** + * Remove current simulation + * + * @param askForConfirmation + * Should we ask for confirmation if a simulation is already active? + */ + public void doRemoveSimulation(boolean askForConfirmation) { + + if (currentSimulation != null) { + if (askForConfirmation) { + String s1 = "Remove"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane.showOptionDialog(frame, + "You have an active simulation.\nDo you want to remove it?", + "Remove current simulation?", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) { + return; + } + } + + // Close all started non-GUI plugins + for (JInternalFrame openededFrame : myGUI.getAllFrames()) { + // Check that frame is a plugin + Class frameClass = openededFrame.getClass(); + if (pluginClasses.contains(frameClass)) { + int pluginType = ((Class) frameClass) + .getAnnotation(VisPluginType.class).value(); + if (pluginType != VisPluginType.GUI_PLUGIN) + removePlugin((VisPlugin) openededFrame, false); + } + } + + // Delete simulation + currentSimulation.deleteObservers(); + currentSimulation.stopSimulation(); + currentSimulation = null; + + // Unregister temporary plugin classes + Enumeration> pluginClasses = pluginClassesTemporary + .elements(); + while (pluginClasses.hasMoreElements()) { + unregisterPlugin(pluginClasses.nextElement()); + } + + // Reset frame title + frame.setTitle("COOJA Simulator"); + } + } + + /** + * Load a simulation configuration file from disk + * + * @param askForConfirmation + * Should we ask for confirmation if a simulation is already active? + */ + public void doLoadConfig(boolean askForConfirmation) { + + if (CoreComm.hasLibraryBeenLoaded()) { + JOptionPane + .showMessageDialog( + frame, + "Shared libraries has already been loaded.\nYou need to restart the simulator!", + "Can't load simulation", JOptionPane.ERROR_MESSAGE); + return; + } + + if (askForConfirmation && currentSimulation != null) { + String s1 = "Remove"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane.showOptionDialog(frame, + "You have an active simulation.\nDo you want to remove it?", + "Remove current simulation?", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) { + return; + } + } + + doRemoveSimulation(false); + + JFileChooser fc = new JFileChooser(); + + fc.setFileFilter(GUI.SAVED_SIMULATIONS_FILES); + + int returnVal = fc.showOpenDialog(frame); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File loadFile = fc.getSelectedFile(); + + // Try adding extension if not founds + if (!loadFile.exists()) { + loadFile = new File(loadFile.getParent(), loadFile.getName() + + SAVED_SIMULATIONS_FILES); + } + + if (loadFile.exists() && loadFile.canRead()) { + Simulation newSim = null; + try { + newSim = Simulation.loadSimulationConfig(loadFile); + } catch (UnsatisfiedLinkError e) { + logger.warn("Could not reopen libraries"); + newSim = null; + } + if (newSim != null) { + myGUI.setSimulation(newSim); + } + } else + logger.fatal("No read access to file"); + + } else { + logger.info("Load command cancelled by user..."); + } + + } + + /** + * Save current simulation configuration to disk + * + * @param askForConfirmation + * Ask for confirmation before overwriting file + */ + public void doSaveConfig(boolean askForConfirmation) { + if (currentSimulation != null) { + currentSimulation.stopSimulation(); + + JFileChooser fc = new JFileChooser(); + + fc.setFileFilter(GUI.SAVED_SIMULATIONS_FILES); + + int returnVal = fc.showSaveDialog(myGUI); + if (returnVal == JFileChooser.APPROVE_OPTION) { + File saveFile = fc.getSelectedFile(); + if (!fc.accept(saveFile)) { + saveFile = new File(saveFile.getParent(), saveFile.getName() + + SAVED_SIMULATIONS_FILES); + } + + if (saveFile.exists()) { + if (askForConfirmation) { + String s1 = "Overwrite"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane + .showOptionDialog( + frame, + "A file with the same name already exists.\nDo you want to remove it?", + "Overwrite existing file?", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) { + return; + } + } + } + + if (!saveFile.exists() || saveFile.canWrite()) + currentSimulation.saveSimulationConfig(saveFile); + else + logger.fatal("No write access to file"); + + } else { + logger.info("Save command cancelled by user..."); + } + } + } + + /** + * Add new mote to current simulation + */ + public void doAddMotes(MoteType moteType) { + if (currentSimulation != null) { + currentSimulation.stopSimulation(); + + Vector newMotes = AddMoteDialog.showDialog(frame, moteType); + if (newMotes != null) { + for (Mote newMote : newMotes) + currentSimulation.addMote(newMote); + } + + } else + logger.warn("No simulation active"); + } + + /** + * Create a new simulation + * + * @param askForConfirmation + * Should we ask for confirmation if a simulation is already active? + */ + public void doCreateSimulation(boolean askForConfirmation) { + if (askForConfirmation && currentSimulation != null) { + String s1 = "Remove"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane.showOptionDialog(frame, + "You have an active simulation.\nDo you want to remove it?", + "Remove current simulation?", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) { + return; + } + } + + // Create new simulation + doRemoveSimulation(false); + Simulation newSim = new Simulation(); + boolean createdOK = CreateSimDialog.showDialog(frame, newSim); + if (createdOK) { + myGUI.setSimulation(newSim); + } + } + + /** + * Quit program + * + * @param askForConfirmation + * Should we ask for confirmation before quitting? + */ + public void doQuit(boolean askForConfirmation) { + if (!askForConfirmation) + System.exit(0); + + String s1 = "Quit"; + String s2 = "Cancel"; + Object[] options = {s1, s2}; + int n = JOptionPane.showOptionDialog(frame, "Sure you want to quit?", + "Close COOJA Simulator", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, s1); + if (n != JOptionPane.YES_OPTION) + return; + + System.exit(0); + } + + // // EXTERNAL TOOLS SETTINGS METHODS //// + + /** + * @return Number of external tools settings + */ + public static int getExternalToolsSettingsCount() { + return externalToolsSettingNames.length; + } + + /** + * Get name of external tools setting at given index. + * + * @param index + * Setting index + * @return Name + */ + public static String getExternalToolsSettingName(int index) { + return externalToolsSettingNames[index]; + } + + /** + * @param name + * Name of setting + * @return Value + */ + public static String getExternalToolsSetting(String name) { + return currentExternalToolsSettings.getProperty(name); + } + + /** + * @param name + * Name of setting + * @param defaultValue + * Default value + * @return Value + */ + public static String getExternalToolsSetting(String name, String defaultValue) { + return currentExternalToolsSettings.getProperty(name, defaultValue); + } + + /** + * @param name + * Name of setting + * @param newVal + * New value + */ + public static void setExternalToolsSetting(String name, String newVal) { + currentExternalToolsSettings.setProperty(name, newVal); + } + + /** + * Load external tools settings from default file. + */ + public static void loadExternalToolsDefaultSettings() { + String filename = GUI.EXTERNAL_TOOLS_LINUX_SETTINGS_FILENAME; + if (System.getProperty("os.name").startsWith("Win")) + filename = GUI.EXTERNAL_TOOLS_WIN32_SETTINGS_FILENAME; + + try { + InputStream in = GUI.class.getResourceAsStream(filename); + if (in == null) { + throw new FileNotFoundException(filename + " not found"); + } + Properties settings = new Properties(); + settings.load(in); + in.close(); + + currentExternalToolsSettings = settings; + } catch (IOException e) { + // Error while importing default properties + logger.warn( + "Error when reading external tools settings from " + filename, e); + } finally { + if (currentExternalToolsSettings == null) { + currentExternalToolsSettings = new Properties(); + } + } + } + + /** + * Load user values from external properties file + */ + private static void loadExternalToolsUserSettings() { + String filename = GUI.EXTERNAL_TOOLS_USER_SETTINGS_FILENAME; + try { + FileInputStream in = new FileInputStream(filename); + Properties settings = new Properties(); + settings.load(in); + in.close(); + + Enumeration en = settings.keys(); + while (en.hasMoreElements()) { + String key = (String) en.nextElement(); + setExternalToolsSetting(key, settings.getProperty(key)); + } + + } catch (FileNotFoundException e) { + // No default configuration file found, using default + } catch (IOException e) { + // Error while importing saved properties, using default + logger.warn("Error when reading default settings from " + filename); + } + } + + /** + * Save external tools user settings to file. + */ + public static void saveExternalToolsUserSettings() { + String filename = GUI.EXTERNAL_TOOLS_USER_SETTINGS_FILENAME; + try { + FileOutputStream out = new FileOutputStream(filename); + currentExternalToolsSettings.store(out, "COOJA User Settings"); + out.close(); + } catch (FileNotFoundException ex) { + // Could not open settings file for writing, aborting + logger.warn("Could not save external tools user settings to " + filename + + ", aborting"); + } catch (IOException ex) { + // Could not open settings file for writing, aborting + logger.warn("Error while saving external tools user settings to " + + filename + ", aborting"); + } + } + + // // GUI EVENT HANDLER //// + + private class GUIEventHandler implements ActionListener, WindowListener { + public void windowDeactivated(WindowEvent e) { + } + public void windowIconified(WindowEvent e) { + } + public void windowDeiconified(WindowEvent e) { + } + public void windowOpened(WindowEvent e) { + } + public void windowClosed(WindowEvent e) { + } + public void windowActivated(WindowEvent e) { + } + + public void windowClosing(WindowEvent e) { + myGUI.doQuit(true); + } + + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("new sim")) { + myGUI.doCreateSimulation(true); + } else if (e.getActionCommand().equals("close sim")) { + myGUI.doRemoveSimulation(true); + } else if (e.getActionCommand().equals("load sim")) { + myGUI.doLoadConfig(true); + } else if (e.getActionCommand().equals("save sim")) { + myGUI.doSaveConfig(true); + } else if (e.getActionCommand().equals("quit")) { + myGUI.doQuit(true); + } else if (e.getActionCommand().equals("create mote type")) { + myGUI.doCreateMoteType((Class) ((JMenuItem) e + .getSource()).getClientProperty("class")); + } else if (e.getActionCommand().equals("add motes")) { + myGUI.doAddMotes((MoteType) ((JMenuItem) e.getSource()) + .getClientProperty("motetype")); + } else if (e.getActionCommand().equals("edit paths")) { + ExternalToolsDialog.showDialog(frame); + } else if (e.getActionCommand().equals("manage platforms")) { + Vector newPlatforms = UserPlatformsDialog.showDialog(frame, + currentUserPlatforms, null); + if (newPlatforms != null) { + currentUserPlatforms = newPlatforms; + reparsePlatformConfig(); + } + } else if (e.getActionCommand().equals("start plugin")) { + Class pluginClass = (Class) ((JMenuItem) e + .getSource()).getClientProperty("class"); + startPlugin(pluginClass); + } else + logger.warn("Unhandled action: " + e.getActionCommand()); + } + } + + // // VARIOUS HELP METHODS //// + + /** + * Help method that tries to load and initialize a class with given name. + * + * @param + * Class extending given class type + * @param classType + * Class type + * @param className + * Class name + * @return Class extending given class type or null if not found + */ + public Class tryLoadClass( + Object callingObject, Class classType, String className) { + + if (callingObject != null) { + try { + return callingObject.getClass().getClassLoader().loadClass(className) + .asSubclass(classType); + } catch (ClassNotFoundException e) { + } + } + + try { + return Class.forName(className).asSubclass(classType); + } catch (ClassNotFoundException e) { + } + + try { + return userPlatformClassLoader.loadClass(className).asSubclass(classType); + } catch (ClassNotFoundException e) { + } + + return null; + } + + private ClassLoader createClassLoader(Vector currentUserPlatforms) { + ClassLoader classLoader = ClassLoader.getSystemClassLoader(); + + // Combine class loader from all user platforms (including any specified JAR + // files) + for (File userPlatform : currentUserPlatforms) { + // Read configuration to check if any JAR files should be loaded + try { + File userPlatformConfigFile = new File(userPlatform.getPath() + + File.separatorChar + PLATFORM_CONFIG_FILENAME); + PlatformConfig userPlatformConfig = new PlatformConfig(); + userPlatformConfig.appendConfig(userPlatformConfigFile); + String[] platformJarFiles = userPlatformConfig.getStringArrayValue( + GUI.class, "JARFILES"); + if (platformJarFiles != null && platformJarFiles.length > 0) { + URL[] platformJarURLs = new URL[platformJarFiles.length]; + for (int i = 0; i < platformJarFiles.length; i++) { + platformJarURLs[i] = new File(userPlatform.getPath() + + File.separatorChar + "lib" + File.separatorChar + + platformJarFiles[i]).toURL(); + } + + classLoader = new URLClassLoader(platformJarURLs, classLoader); + } + + } catch (Exception e) { + logger.fatal("Error when trying to read JAR-file in " + userPlatform + + ": " + e); + } + + // Add java class directory + classLoader = new DirectoryClassLoader(classLoader, new File(userPlatform + .getPath() + + File.separatorChar + "java")); + } + + return classLoader; + } + + /** + * Help method that returns the description for given object. This method + * reads from the object's class annotations if existing. Otherwise it returns + * the simple class name of object's class. + * + * @param object + * Object + * @return Description + */ + public static String getDescriptionOf(Object object) { + return getDescriptionOf(object.getClass()); + } + + /** + * Help method that returns the description for given class. This method reads + * from class annotations if existing. Otherwise it returns the simple class + * name. + * + * @param clazz + * Class + * @return Description + */ + public static String getDescriptionOf(Class clazz) { + if (clazz.isAnnotationPresent(ClassDescription.class)) { + return clazz.getAnnotation(ClassDescription.class).value(); + } + return clazz.getSimpleName(); + } + + /** + * Load configurations and create a GUI. + * + * @param args + * null + */ + public static void main(String[] args) { + + // Configure logger + if ((new File(LOG_CONFIG_FILE)).exists()) { + DOMConfigurator.configure(LOG_CONFIG_FILE); + } else { + // Used when starting from jar + DOMConfigurator.configure(GUI.class.getResource("/" + LOG_CONFIG_FILE)); + } + + // Schedule a job for the event-dispatching thread: + // creating and showing this application's GUI. + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + createAndShowGUI(); + } + }); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/IPDistributor.java b/tools/cooja/java/se/sics/cooja/IPDistributor.java new file mode 100644 index 000000000..1e6cb8f8a --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/IPDistributor.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: IPDistributor.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.lang.reflect.Constructor; +import java.util.Vector; +import org.apache.log4j.Logger; + +/** + * A IP distributor is used for determining IP addresses of newly created motes. + * + * @author Fredrik Osterlind + */ +public abstract class IPDistributor { + private static Logger logger = Logger.getLogger(IPDistributor.class); + + /** + * This method creates an instance of the given class with the given vector as + * constructor argument. Instead of calling the constructors directly this + * method may be used. + * + * @param ipDistClass Class + * @param newMotes All motes that later should be assigned IP numbers + * @return IP distributor instance + */ + public static final IPDistributor generateInterface( + Class ipDistClass, Vector newMotes) { + try { + // Generating IP distributor + Constructor constr = ipDistClass + .getConstructor(new Class[]{Vector.class}); + return (IPDistributor) constr.newInstance(new Object[]{newMotes}); + } catch (Exception e) { + logger.fatal("Exception when creating " + ipDistClass + ": " + e); + return null; + } + } + + /** + * Returns the next mote position. + * + * @return Position + */ + public abstract String getNextIPAddress(); + +} diff --git a/tools/cooja/java/se/sics/cooja/Mote.java b/tools/cooja/java/se/sics/cooja/Mote.java new file mode 100644 index 000000000..2f75cd4cd --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/Mote.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Mote.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.util.Collection; +import java.util.Observer; +import org.jdom.Element; + +/** + * This interface represents a simulated mote. + * + * A mote is always in some state, describing the status of the CPU etc. + * Motes in different states may be handled differently by the for example simulation loops and plugins. + * + * All motes must also have an interface handler, a mote type and a mote memory. + * + * @see se.sics.cooja.MoteInterfaceHandler + * @see se.sics.cooja.MoteMemory + * @see se.sics.cooja.MoteType + * + * @author Fredrik Osterlind + */ +public interface Mote { + + /** + * Active state. + */ + public static int STATE_ACTIVE = 1; + + /** + * Sleeping state. + */ + public static int STATE_LPM = 2; + + /** + * Dead state (may be out of batteries). + */ + public static int STATE_DEAD = 3; + + /** + * Tries to change state to given argument. + * A dead mote can typically not change state, while a sleeping or active mote can. + * + * @param newState New state of mote. + */ + public void setState(int newState); + + /** + * @return Current mote state + */ + public int getState(); + + /** + * Adds new state observer. + * This observer is notified if mote changes state. + * + * @see #deleteStateObserver(Observer) + * @param newObserver New observer + */ + public void addStateObserver(Observer newObserver); + + /** + * Delete existing state observer. + * + * @see #addStateObserver(Observer) + * @param newObserver Registered state observer + */ + public void deleteStateObserver(Observer newObserver); + + + /** + * Returns the interface handler of this mote. + * + * @see #setInterfaces(MoteInterfaceHandler) + * @return Mote interface handler + */ + public MoteInterfaceHandler getInterfaces(); + + /** + * Sets the interface handler of this mote. + * + * @param moteInterfaceHandler New interface handler + * @see #getInterfaces() + */ + public void setInterfaces(MoteInterfaceHandler moteInterfaceHandler); + + + /** + * Returns the memory of this mote. + * + * @see #setMemory(MoteMemory) + * @return Mote memory + */ + public MoteMemory getMemory(); + + /** + * Sets the memory of this mote. + * + * @see #getMemory() + * @param memory Mote memory + */ + public void setMemory(MoteMemory memory); + + + /** + * Returns mote type. + * + * @see #setType(MoteType) + * @return Mote type + */ + public MoteType getType(); + + /** + * Sets mote type to given argument. + * + * @see #getType() + * @param type New type + */ + public void setType(MoteType type); + + + /** + * Returns simulation which holds this mote. + * + * @see #setSimulation(Simulation) + * @return Simulation + */ + public Simulation getSimulation(); + + /** + * Sets the simulation which holds this mote. + * + * @see #getSimulation() + * @param simulation Simulation + */ + public void setSimulation(Simulation simulation); + + /** + * Ticks this mote and increases any internal time to given argument. + * + * Each mote implementation may handle calls to this method differently, + * but, if existing, the simulated mote should at least handle one event. + * + * This method is responsible for updating the mote interfaces, the memory and the mote state. + * + * A call to this method typically + * polls all interfaces, + * activates the memory, + * lets the underlying mote software handle one event, + * fetches the updated memory and + * finally polls all interfaces again. + * + * @param simTime New simulation time + */ + public void tick(int simTime); + + /** + * Returns XML elements representing the current config of this mote. + * This is fetched by the simulator for example when saving a simulation configuration file. + * For example a mote may return the configs of all its interfaces. + * This method should however not return state specific information such as the mote state. + * (All nodes are restarted when loading a simulation.) + * + * @see #setConfigXML(Simulation, Collection) + * @return XML elements representing the current mote config + */ + public abstract Collection getConfigXML(); + + /** + * Sets the current mote config depending on the given XML elements. + * + * @param simulation Simulation holding this mote + * @param configXML Config XML elements + * + * @see #getConfigXML() + */ + public abstract boolean setConfigXML(Simulation simulation, Collection configXML); + +} diff --git a/tools/cooja/java/se/sics/cooja/MoteInterface.java b/tools/cooja/java/se/sics/cooja/MoteInterface.java new file mode 100644 index 000000000..623e6ce10 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/MoteInterface.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteInterface.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.util.Collection; +import java.util.Observable; +import javax.swing.JPanel; +import org.apache.log4j.Logger; +import org.jdom.Element; + +/** + * A mote interface represents a mote property. + * Often this is a simulated hardware peripheral such as a button or a led. + * This can also be a property the mote software itself is unaware of, + * for example the current position of the mote. + * + * Interfaces are the main way for the simulator to interact with a simulated mote. + * + * Interfaces are divided into active and passive interfaces, and are handled differently. + * In order to create a passive interfaces, the class should also implement the dummy Java interface PassiveMoteInterface. + * For an explanation of the differences of active and passive interfaces see class PassiveMoteInterface. + * + * @see PassiveMoteInterface + * @author Fredrik Osterlind + */ +public abstract class MoteInterface extends Observable { + private static Logger logger = Logger.getLogger(MoteInterface.class); + + /** + * This method creates an instance of the given class with the given mote as constructor + * argument. Instead of calling the interface constructors directly this method may be used. + * + * @param interfaceClass Mote interface class + * @param mote Mote that will hold the interface + * @return Mote interface instance + */ + public static final MoteInterface generateInterface(Class interfaceClass, Mote mote) { + try { + // Generating interface + MoteInterface instance = (MoteInterface) interfaceClass.getConstructor( + new Class[] { Mote.class }).newInstance(new Object[] { mote }); + + return instance; + } catch (Exception e) { + logger.fatal("Exception when creating " + interfaceClass + ": " + e); + return null; + } + } + + /** + * Actions to be performed just before the holding mote is ticked + */ + public abstract void doActionsBeforeTick(); + + /** + * Actions to be performed just after the holding mote has been ticked + */ + public abstract void doActionsAfterTick(); + + /** + * Returns a panel with interesting data for this interface. + * This could for example show last messages sent/received for + * a radio interface, or logged message for a log interface. + * + * All panels returned from this method must later be released + * for memory reasons. + * + * If returned panel is null, this interface will not be visualized. + * + * @see #releaseInterfaceVisualizer(JPanel) + * @return Interface visualizer or null + */ + public abstract JPanel getInterfaceVisualizer(); + + /** + * This method should be called when a visualizer panel is no longer in use. + * Any resources of that panel, for example registered observers, will be released. + * + * @see #getInterfaceVisualizer() + * @param panel A interface visualizer panel fetched earlier for this mote interface. + */ + public abstract void releaseInterfaceVisualizer(JPanel panel); + + /** + * Returns approximated energy consumed (mQ) during the current tick. + * If the interface is active, this information must be available after the doActionsAfterTick method. + * If the interface is passive, this information must be available after the doActionsBeforeTick method. + * + * The interface is responsible to gather information about the current internal state, + * and calculate whatever energy it needs in that state and during one tick. + * + * If the holding mote is dead, this method will not be called. + * If the holding mote is sleeping and this interface is active, this method will not be called. + * + * For example, a radio transmitter or a PIR sensor often has a much higher energy + * usage than a button sensor which virtually needs no energy at all. + * If the radio is turned off in hardware, it should return a zero energy consumption. + * If the radio is sending something which would take longer than one tick, it may either return + * the total energy used directly, or a smaller amount during several ticks. + * + * This method may typically be used by the passive interface battery, which sums up + * all energy used during one tick and decreases the battery energy left. + * + * @see se.sics.cooja.interfaces.Battery + * @return Energy consumption of this device during the current tick + */ + public abstract double energyConsumptionPerTick(); + + /** + * Returns XML elements representing the current config of this mote interface. + * This is fetched by the simulator for example when saving a simulation configuration file. + * For example an IP interface may return one element with the mote IP address. + * This method should however not return state specific information such as a log history. + * (All nodes are restarted when loading a simulation.) + * + * @see #setConfigXML(Collection) + * @return XML elements representing the current interface config + */ + public abstract Collection getConfigXML(); + + /** + * Sets the current mote interface config depending on the given XML elements. + * + * @see #getConfigXML() + * @param configXML Config XML elements + */ + public abstract void setConfigXML(Collection configXML); + +} diff --git a/tools/cooja/java/se/sics/cooja/MoteInterfaceHandler.java b/tools/cooja/java/se/sics/cooja/MoteInterfaceHandler.java new file mode 100644 index 000000000..c4f45ded6 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/MoteInterfaceHandler.java @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteInterfaceHandler.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.util.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.interfaces.*; + +/** + * A mote interface handler holds all interfaces for a specific mote. Even + * though an interface handler strictly does not need any interfaces at all, a + * position interface is highly recommended. (A lot of plugins depend on a mote + * position, for example when visualizing nodes.) + * + * Interfaces are divided into active and passive interfaces. Active interfaces + * are only polled if the mote is active, while passive interfaces are polled in + * all states except dead state. + * + * Interfaces should be polled via this class when the mote is ticked. + * + * @author Fredrik Osterlind + */ +public class MoteInterfaceHandler { + private static Logger logger = Logger.getLogger(MoteInterfaceHandler.class); + + private Battery myBattery; + private Beeper myBeeper; + private Button myButton; + private Clock myClock; + private IPAddress myIPAddress; + private LED myLED; + private Log myLog; + private MoteID myMoteID; + private PIR myPIR; + private Position myPosition; + private Radio myRadio; + + private Vector myActiveInterfaces = new Vector(); + private Vector myPassiveInterfaces = new Vector(); + + /** + * Creates a new empty mote interface handler. + */ + public MoteInterfaceHandler() { + } + + /** + * Creates a new mote interface handler. All given interfaces are loaded. + * + * @param mote + * The mote holding this interface handler + * @param allInterfaces + * Simulation interfaces to load + */ + public MoteInterfaceHandler(Mote mote, + Vector> allInterfaces) { + + // Load all interfaces + for (Class interfaceClass : allInterfaces) { + boolean isPassive = false; + + // Check if interface is active or passive + if (PassiveMoteInterface.class.isAssignableFrom(interfaceClass)) + isPassive = true; + + // Load interface + MoteInterface loadedInterface = MoteInterface.generateInterface( + interfaceClass, mote); + + if (loadedInterface != null) + if (isPassive) + addPassiveInterface(loadedInterface); + else + addActiveInterface(loadedInterface); + else + logger.warn("Interface could not be loaded: " + interfaceClass); + } + } + + /** + * Get an interface (active or passive) of the given type. Returns the first + * loaded interface found, that is either of the given class or of a subclass. + * + * For example, if the current radio interface is wanted, this method would be + * called like the following: getInterfaceOfType(Radio.class) + * + * @param interfaceType + * Type of interface to return + * @return Interface or null if no interface loaded of given type + */ + public N getInterfaceOfType(Class interfaceType) { + + Enumeration allActive = myActiveInterfaces.elements(); + while (allActive.hasMoreElements()) { + N nextInterface = (N) allActive.nextElement(); + if (interfaceType.isAssignableFrom(nextInterface.getClass())) + return nextInterface; + } + Enumeration allPassive = myPassiveInterfaces.elements(); + while (allPassive.hasMoreElements()) { + N nextInterface = (N) allPassive.nextElement(); + if (interfaceType.isAssignableFrom(nextInterface.getClass())) + return nextInterface; + } + return null; + } + + /** + * Returns the battery interface (if any). + * + * @return Battery interface + */ + public Battery getBattery() { + if (myBattery == null) { + myBattery = getInterfaceOfType(Battery.class); + } + return myBattery; + } + + /** + * Returns the beeper interface (if any). + * + * @return Beeper interface + */ + public Beeper getBeeper() { + if (myBeeper == null) { + myBeeper = getInterfaceOfType(Beeper.class); + } + return myBeeper; + } + + /** + * Returns the button interface (if any). + * + * @return Button interface + */ + public Button getButton() { + if (myButton == null) { + myButton = getInterfaceOfType(Button.class); + } + return myButton; + } + + /** + * Returns the clock interface (if any). + * + * @return Clock interface + */ + public Clock getClock() { + if (myClock == null) { + myClock = getInterfaceOfType(Clock.class); + } + return myClock; + } + + /** + * Returns the IP address interface (if any). + * + * @return IPAddress interface + */ + public IPAddress getIPAddress() { + if (myIPAddress == null) { + myIPAddress = getInterfaceOfType(IPAddress.class); + } + return myIPAddress; + } + + /** + * Returns the LED interface (if any). + * + * @return LED interface + */ + public LED getLED() { + if (myLED == null) { + myLED = getInterfaceOfType(LED.class); + } + return myLED; + } + + /** + * Returns the log interface (if any). + * + * @return Log interface + */ + public Log getLog() { + if (myLog == null) { + myLog = getInterfaceOfType(Log.class); + } + return myLog; + } + + /** + * Returns the mote ID interface (if any). + * + * @return Mote ID interface + */ + public MoteID getMoteID() { + if (myMoteID == null) { + myMoteID = getInterfaceOfType(MoteID.class); + } + return myMoteID; + } + + /** + * Returns the PIR interface (if any). + * + * @return PIR interface + */ + public PIR getPIR() { + if (myPIR == null) { + myPIR = getInterfaceOfType(PIR.class); + } + return myPIR; + } + + /** + * Returns the position interface (if any). + * + * @return Position interface + */ + public Position getPosition() { + if (myPosition == null) { + myPosition = getInterfaceOfType(Position.class); + } + return myPosition; + } + + /** + * Returns the radio interface (if any). + * + * @return Radio interface + */ + public Radio getRadio() { + if (myRadio == null) { + myRadio = getInterfaceOfType(Radio.class); + } + return myRadio; + } + + /** + * Polls all active interfaces. This method should be called during a mote + * tick before the mote software is executed. + */ + public void doActiveActionsBeforeTick() { + for (int i = 0; i < myActiveInterfaces.size(); i++) + myActiveInterfaces.get(i).doActionsBeforeTick(); + } + + /** + * Polls all active interfaces. This method should be called during a mote + * tick after the mote software has executed. + */ + public void doActiveActionsAfterTick() { + for (int i = 0; i < myActiveInterfaces.size(); i++) + myActiveInterfaces.get(i).doActionsAfterTick(); + } + + /** + * Polls all passive interfaces. This method should be called during a mote + * tick before the mote software is executed. + */ + public void doPassiveActionsBeforeTick() { + for (int i = 0; i < myPassiveInterfaces.size(); i++) + myPassiveInterfaces.get(i).doActionsBeforeTick(); + } + + /** + * Polls all passive interfaces. This method should be called during a mote + * tick after the mote software has executed. + */ + public void doPassiveActionsAfterTick() { + for (int i = 0; i < myPassiveInterfaces.size(); i++) + myPassiveInterfaces.get(i).doActionsAfterTick(); + } + + /** + * Returns all passive mote interfaces. + * + * @return All passive mote interface + */ + public Vector getAllPassiveInterfaces() { + return myPassiveInterfaces; + } + + /** + * Returns all active mote interfaces. + * + * @return All active mote interface + */ + public Vector getAllActiveInterfaces() { + return myActiveInterfaces; + } + + /** + * Add an active interface to corresponding mote. An active interface is only + * allowed to act if the mote is in active state. However, since interfaces + * may awaken a sleeping mote up via external interrupts, most of the + * interfaces should be active. + * + * For example a button interface should be active. When the button is + * pressed, the interface will wake the mote up (simulated external + * interrupt), and then that button will be allowed to act before next tick. + * + * A passive interface is an interface which will always act if the mote is + * not dead. For example a battery should always be allowed to act since a + * mote needs energy even if it is in sleep mode. + * + * @see #addPassiveInterface(MoteInterface) + * @param newInterface + * New interface + */ + public void addActiveInterface(MoteInterface newInterface) { + myActiveInterfaces.add(newInterface); + } + + /** + * Add a passive interface to corresponding mote. For explanation of passive + * vs active interfaces, see addActiveInterface(MoteInterface). + * + * @see #addActiveInterface(MoteInterface) + * @param newInterface + * New interface + */ + public void addPassiveInterface(MoteInterface newInterface) { + myPassiveInterfaces.add(newInterface); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/MoteMemory.java b/tools/cooja/java/se/sics/cooja/MoteMemory.java new file mode 100644 index 000000000..3f69b93b0 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/MoteMemory.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteMemory.java,v 1.1 2006/08/21 12:12:55 fros4943 Exp $ + */ + +package se.sics.cooja; + +/** + * This interface represents a mote memory. + * + * Mote memory is represented by byte arrays and this + * interface provides a few of basic operations. + * + * Note that this memory internally may consist of several + * different memory sections, not covering the entire range + * between the start address and the end address of this memory. + * + * @author Fredrik Osterlind + */ +public interface MoteMemory { + + /** + * Clears the entire memory. + */ + public void clearMemory(); + + /** + * Returns a memory segment. + * + * @param address Start address of memory segment + * @param size Size of memory segment + * @return Memory segment or null if segment not available + */ + public byte[] getMemorySegment(int address, int size); + + /** + * Sets a memory segment. + * + * @param address Start address of memory segment + * @param data Data + */ + public void setMemorySegment(int address, byte[] data); + + /** + * Returns the sum of all byte array sizes in this memory. + * This is not neccessarily the the same as the total memory range, + * since the entire memory range may not be handled by this memory. + * + * @return Total size + */ + public int getTotalSize(); + +} diff --git a/tools/cooja/java/se/sics/cooja/MoteType.java b/tools/cooja/java/se/sics/cooja/MoteType.java new file mode 100644 index 000000000..7242dd5cb --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/MoteType.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteType.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.util.Collection; +import javax.swing.JFrame; +import javax.swing.JPanel; +import org.jdom.Element; + +/** + * Every simulated motes belongs to a mote type. + * + * The mote type defines properties common for several motes. These properties + * may differ between different implementations, but typically includes how a + * mote of that type is initialized, which hardware peripherals each mote has + * etc. + * + * A mote type may also hold the connection to an underlying simulation + * framework. + * + * @author Fredrik Osterlind + */ +public interface MoteType { + + /** + * Returns the mote type description. + * + * @return Description + */ + public String getDescription(); + + /** + * Sets the mote type description. + * + * @param description + * New description + */ + public void setDescription(String description); + + /** + * Returns the mote type identifier. + * + * @return Mote type identifier + */ + public String getIdentifier(); + + /** + * Sets the mote type identifier. + * + * @param identifier + * New identifier + */ + public void setIdentifier(String identifier); + + /** + * Returns a panel with interesting data for this mote type. + * + * @return Mote type visualizer + */ + public JPanel getTypeVisualizer(); + + /** + * Returns this mote type's platform configuration. + * + * @return Platform configuration + */ + public PlatformConfig getConfig(); + + /** + * Generates a mote of this mote type. + * + * @param simulation + * Newly created mote's simulation + * @return New mote + */ + public Mote generateMote(Simulation simulation); + + /** + * This method configures and initializes a mote type ready to be used. It is + * called from the simulator when a new mote type is created. It may simply + * confirm that all settings are valid and return true, or display a dialog + * allowing a user to manually configure the mote type. + * + * This method need normally only be run once per mote type! + * + * @param parentFrame + * Parent frame or null + * @param simulation + * Simulation holding (or that should hold) mote type + * @return True if mote type has valid settings and is ready to be used + */ + public boolean configureAndInit(JFrame parentFrame, Simulation simulation); + + /** + * Returns XML elements representing the current config of this mote type. + * This is fetched by the simulator for example when saving a simulation + * configuration file. For example a Contiki base directory may be saved. + * + * @see #setConfigXML(Simulation, Collection) + * @return XML elements representing the current mote type's config + */ + public Collection getConfigXML(); + + /** + * Sets the current mote type config depending on the given XML elements. + * Observe that this method is responsible for restoring the configuration + * depending on the given arguments. This may include recompiling and loading + * libraries. + * + * @see #getConfigXML() + * @param simulation + * Simulation that will hold the mote type + * @param configXML + * Config XML elements + * @return True if config was set successfully, false otherwise + */ + public boolean setConfigXML(Simulation simulation, + Collection configXML); + +} diff --git a/tools/cooja/java/se/sics/cooja/PassiveMoteInterface.java b/tools/cooja/java/se/sics/cooja/PassiveMoteInterface.java new file mode 100644 index 000000000..fc4aa0989 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/PassiveMoteInterface.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: PassiveMoteInterface.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja; + +/** + * Mote interfaces are divided into active and passive interfaces. + * + * A passive mote interface is treated different than an ordinary (active) + * mote interface; + * Passive interfaces are allowed to act even when the mote is sleeping, + * while active interface only acts when the mote is in active state. + * + * A typical active interface is the radio interface, since radio + * messages only can be received when the mote is active. + * + * A typical passive interface is the battery interface, since a mote + * consumes energy even though it is sleeping. + * + * All passive interface should implement this interface. + * All interfaces not implemented this interface will be handled as active interfaces. + * + * Any energy required by this interface must be available after the + * doActionsBeforeTick method. + * + * @author Fredrik Osterlind + */ +public interface PassiveMoteInterface { + +} diff --git a/tools/cooja/java/se/sics/cooja/PlatformConfig.java b/tools/cooja/java/se/sics/cooja/PlatformConfig.java new file mode 100644 index 000000000..64cd7156d --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/PlatformConfig.java @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: PlatformConfig.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.io.*; +import java.util.*; +import org.apache.log4j.Logger; + +/** + * A platform configuration may hold the configuration for one or several user + * platforms as well as a general simulator configuration. + * + * The configuration for a user platform may for example consist of which + * plugins, interfaces and processes that the specific platform supplies. Each + * user platform configuration is read from the property file cooja.config, a + * file which is required in each user platform. + * + * Values can be fetched as String, Boolean, Integer, Double or String array. + * + * Several configurations can be merged, together forming a final overall + * configuration. The order of the how configurations are merged matter - later + * values will overwrite earlier. For example merging two configurations with + * the key 'SOMEKEY' in the following order: + * + * SOMEKEY = a b c + * + * SOMEKEY = d e + * + * will result in the final value "d e". + * + * If a specific value should be extended instead of overwritten, the value must + * start with a single space-surrounded '+'. For example, merging two + * configurations with the key as above in the following order: + * + * SOMEKEY = a b c + * + * SOMEKEY = + d e + * + * will result in the final value "a b c d e". + * + * The simulator will hold a merged platform configuration, depending on which + * user platforms are used. Additionally. each mote type may also have a + * configuration of its own, that differs from the general simulator + * configuration. + * + * Often, but not necessarily, keys are named depending on which class is + * associated with the information. For example, let's say a battery interface + * wants to store its initial capacity (a double) using this approach. Data + * stored in the external configuration file can look like the following: + * se.sics.cooja.interfaces.Battery.initial_capacity 54.123321 + * + * This value is then be read by: myMoteTypeConfig.getDoubleValue(Battery.class, + * "initial_capacity"); + * + * @author Fredrik Osterlind + */ +public class PlatformConfig { + private static Logger logger = Logger.getLogger(PlatformConfig.class); + + private Properties myConfig = new Properties(); + + /** + * Creates a new empty platform configuration. + */ + public PlatformConfig() { + myConfig = new Properties(); + } + + /** + * Loads the given property file and appends it to the current configuration. + * If a property already exists it will be overwritten, unless the new value + * begins with a '+' in which case the old value will be extended. + * + * @param propertyFile + * Property file to read + * @return True if file was read ok, false otherwise + * @throws FileNotFoundException + * If file was not found + * @throws IOException + * Stream read error + */ + public boolean appendConfig(File propertyFile) throws FileNotFoundException, + IOException { + return appendConfig(myConfig, propertyFile); + } + + private static boolean appendConfig(Properties currentValues, + File propertyFile) throws FileNotFoundException, IOException { + // Open file + FileInputStream in = new FileInputStream(propertyFile); + return appendConfig(currentValues, in); + } + + /** + * Reads propertues from the given stream and appends them to the current + * configuration. If a property already exists it will be overwritten, unless + * the new value begins with a '+' in which case the old value will be + * extended. + * + * @param configFileStream + * Stream to read from + * @return True if stream was read ok, false otherwise + * @throws IOException + * Stream read error + */ + public boolean appendConfig(InputStream configFileStream) throws IOException { + return appendConfig(myConfig, configFileStream); + } + + private static boolean appendConfig(Properties currentValues, + InputStream configFileStream) throws IOException { + + // Read from stream + Properties newProps = new Properties(); + newProps.load(configFileStream); + configFileStream.close(); + + // Read new properties + Enumeration en = newProps.keys(); + while (en.hasMoreElements()) { + String key = (String) en.nextElement(); + String property = newProps.getProperty(key); + if (property.startsWith("+ ")) { + if (currentValues.getProperty(key) != null) + currentValues.setProperty(key, currentValues.getProperty(key) + " " + property.substring(1).trim()); + else + currentValues.setProperty(key, property.substring(1).trim()); + } else + currentValues.setProperty(key, property); + } + + return true; + } + + /** + * @return All property names in configuration + */ + public Enumeration getPropertyNames() { + return (Enumeration) myConfig.propertyNames(); + } + + /** + * Get string value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @param defaultValue + * Default value to return if id is not found + * @return Value or defaultValue if id wasn't found + */ + public String getStringValue(Class callingClass, String id, + String defaultValue) { + return getStringValue(myConfig, callingClass, id, defaultValue); + } + + private static String getStringValue(Properties currentValues, + Class callingClass, String id, String defaultValue) { + String val = currentValues.getProperty(callingClass.getName() + "." + id); + + if (val == null) { + logger.warn("Could not find key named '" + callingClass.getName() + "." + id + "'"); + return defaultValue; + } + + return val; + } + + /** + * Returns value of given name. + * + * @param name + * Name + * @return Value as string + */ + public String getStringValue(String name) { + if (!myConfig.containsKey(name)) + logger.debug("Could not find key named '" + name + "'"); + + return myConfig.getProperty(name); + } + + /** + * Get string value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or null if id wasn't found + */ + public String getStringValue(Class callingClass, String id) { + return getStringValue(callingClass, id, null); + } + + /** + * Get string array value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or null if id wasn't found + */ + public String[] getStringArrayValue(Class callingClass, String id) { + String stringVal = getStringValue(callingClass, id, null); + if (stringVal == null) + return new String[0]; + + return getStringValue(callingClass, id, "").split(" "); + } + + /** + * Get string value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or null if id wasn't found + */ + public String getValue(Class callingClass, String id) { + return getStringValue(callingClass, id); + } + + /** + * Get integer value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @param defaultValue + * Default value to return if id is not found + * @return Value or defaultValue if id wasn't found + */ + public int getIntegerValue(Class callingClass, String id, int defaultValue) { + String str = getStringValue(callingClass, id); + if (str == null) + return defaultValue; + + return Integer.parseInt(str); + } + + /** + * Get integer value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or 0 if id wasn't found + */ + public int getIntegerValue(Class callingClass, String id) { + return getIntegerValue(callingClass, id, 0); + } + + /** + * Get double value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @param defaultValue + * Default value to return if id is not found + * @return Value or defaultValue if id wasn't found + */ + public double getDoubleValue(Class callingClass, String id, + double defaultValue) { + String str = getStringValue(callingClass, id); + if (str == null) + return defaultValue; + + return Double.parseDouble(str); + } + + /** + * Get double value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or 0.0 if id wasn't found + */ + public double getDoubleValue(Class callingClass, String id) { + return getDoubleValue(callingClass, id, 0.0); + } + + /** + * Get boolean value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @param defaultValue + * Default value to return if id is not found + * @return Value or defaultValue if id wasn't found + */ + public boolean getBooleanValue(Class callingClass, String id, + boolean defaultValue) { + String str = getStringValue(callingClass, id); + if (str == null) + return defaultValue; + + return Boolean.parseBoolean(str); + } + + /** + * Get boolean value with given id. + * + * @param callingClass + * Class which value belongs to + * @param id + * Id of value to return + * @return Value or false if id wasn't found + */ + public boolean getBooleanValue(Class callingClass, String id) { + return getBooleanValue(callingClass, id, false); + } + + public PlatformConfig clone() { + PlatformConfig clone = new PlatformConfig(); + clone.myConfig = (Properties) this.myConfig.clone(); + return clone; + } +} diff --git a/tools/cooja/java/se/sics/cooja/Positioner.java b/tools/cooja/java/se/sics/cooja/Positioner.java new file mode 100644 index 000000000..6c6968a69 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/Positioner.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Positioner.java,v 1.1 2006/08/21 12:12:51 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.lang.reflect.Constructor; +import org.apache.log4j.Logger; + +/** + * A positioner is used for determining positions of newly created motes. + * + * @author Fredrik Osterlind + */ +public abstract class Positioner { + private static Logger logger = Logger.getLogger(Positioner.class); + + /** + * This method creates an instance of the given class with the given interval + * information as constructor arguments. Instead of calling the constructors + * directly this method may be used. + * + * @param positionerClass + * Positioner class + * @param totalNumberOfMotes + * Total number of motes that should be generated using this + * positioner + * @param startX + * Lowest X value of positions generated using returned positioner + * @param endX + * Highest X value of positions generated using returned positioner + * @param startY + * Lowest Y value of positions generated using returned positioner + * @param endY + * Highest Y value of positions generated using returned positioner + * @param startZ + * Lowest Z value of positions generated using returned positioner + * @param endZ + * Highest Z value of positions generated using returned positioner + * @return Postioner instance + */ + public static final Positioner generateInterface( + Class positionerClass, int totalNumberOfMotes, + double startX, double endX, double startY, double endY, double startZ, + double endZ) { + try { + // Generating positioner + Constructor constr = positionerClass.getConstructor(new Class[]{ + int.class, double.class, double.class, double.class, double.class, + double.class, double.class}); + return (Positioner) constr.newInstance(new Object[]{totalNumberOfMotes, + startX, endX, startY, endY, startZ, endZ}); + } catch (Exception e) { + logger.fatal("Exception when creating " + positionerClass + ": " + e); + return null; + } + } + + /** + * Returns the next mote position. + * + * @return Position + */ + public abstract double[] getNextPosition(); + +} diff --git a/tools/cooja/java/se/sics/cooja/RadioConnection.java b/tools/cooja/java/se/sics/cooja/RadioConnection.java new file mode 100644 index 000000000..63b4c6524 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/RadioConnection.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: RadioConnection.java,v 1.1 2006/08/21 12:12:55 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.util.Vector; + +import se.sics.cooja.interfaces.Position; +import se.sics.cooja.interfaces.Radio; + +/** + * RadioConnection represents a radio connection between a sending radio + * and zero or more receiving radios. + * By registering as an observer to the current radio medium, all + * radio connections and data sent in that medium can be accessed. + * + * Each radio is associated with a position and some radio data. + * Often the destinations' and source's data will refer to the same object, + * but some radio mediums may want to distort the transferred data, hence + * resulting in different data sent and received. + * + * @see RadioMedium + * @author Fredrik Osterlind + */ +public class RadioConnection { + private Radio sourceRadio; + private Position sourcePosition; + private byte[] sourceData; + + private Vector destinationRadios = new Vector(); + private Vector destinationPositions = new Vector(); + private Vector destinationData = new Vector(); + + /** + * Set source of this connection. + * + * @param radio Source radio + * @param position Source position + * @param data Source data + */ + public void setSource(Radio radio, Position position, byte[] data) { + sourceRadio = radio; + sourcePosition = position; + sourceData = data; + } + + /** + * Add a connection destination. + * + * @param radio Source radio + * @param position Source position + * @param data Source data + */ + public void addDestination(Radio radio, Position position, byte[] data) { + destinationRadios.add(radio); + destinationPositions.add(position); + destinationData.add(data); + } + + /** + * @return Source radio + */ + public Radio getSourceRadio() { + return sourceRadio; + } + + /** + * @return Source position + */ + public Position getSourcePosition() { + return sourcePosition; + } + + /** + * Returns the data actually sent by source radio. + * @return Source data + */ + public byte[] getSourceData() { + return sourceData; + } + + /** + * @return Array of destination radios + */ + public Radio[] getDestinationRadios() { + Radio[] radioArrayType; + Radio[] radioArray; + + radioArrayType = new Radio[destinationRadios.size()]; + radioArray = (Radio[]) destinationRadios.toArray(radioArrayType); + + return radioArray; + } + + /** + * @return Array of destination positions + */ + public Position[] getDestinationPositons() { + Position[] positionArrayType; + Position[] positionArray; + + positionArrayType = new Position[destinationPositions.size()]; + positionArray = (Position[]) destinationPositions.toArray(positionArrayType); + + return positionArray; + } + + /** + * Returns an array of data actually received by each radio. + * @return Array of destination data + */ + public byte[][] getDestinationData() { + byte[][] dataArrayType; + byte[][] dataArray; + + dataArrayType = new byte[destinationData.size()][]; + dataArray = (byte[][]) destinationData.toArray(dataArrayType); + + return dataArray; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/RadioMedium.java b/tools/cooja/java/se/sics/cooja/RadioMedium.java new file mode 100644 index 000000000..6af056dcf --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/RadioMedium.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: RadioMedium.java,v 1.1 2006/08/21 12:12:55 fros4943 Exp $ + */ + +package se.sics.cooja; +import java.util.Collection; +import java.util.Observer; + +import org.jdom.Element; + +import se.sics.cooja.interfaces.Position; +import se.sics.cooja.interfaces.Radio; + +/** + * This interface represents a radio medium. Radios registered to this medium + * can both send and receive radio data. Depending on the implementation of this + * interface, more or less accurate radio behaviour imitation is aquired. + * + * Often a radio medium, at initialization, registers one or several dynamic + * plugins. These plugins shows the user some radio medium specific details, + * such as radio transmission radius etc. + * + * @author Fredrik Osterlind + */ +public abstract class RadioMedium { + + /** + * Registers a mote to this medium. + * + * How radio data will be received from and sent to this mote depends on the + * medium implementation. Common factors may be random, distance from sending + * to receiving mote and interference with other radio transmitters in some + * range. + * + * @param mote + * Mote to register + * @param sim + * Simulation holding mote + */ + public abstract void registerMote(Mote mote, Simulation sim); + + /** + * Unregisters a mote from this medium. + * + * @param mote + * Mote to unregister + * @param sim + * Simulation holding mote + */ + public abstract void unregisterMote(Mote mote, Simulation sim); + + /** + * Register a radio to this medium at a given position. + * + * Concerning radio data, this radio will be treated the same way as a mote's + * radio. This method can be used to add non-mote radio devices, such as a + * packet generator or a sniffer. + * + * @param radio + * Radio + * @param position + * Position + * @param sim + * Simulation holding radio + */ + public abstract void registerRadioInterface(Radio radio, Position position, + Simulation sim); + + /** + * Unregisters a radio interface from this medium. + * + * @param radio + * Radio interface to unregister + * @param sim + * Simulation holding radio + */ + public abstract void unregisterRadioInterface(Radio radio, Simulation sim); + + /** + * Adds an observer which is notified after the radio connections has been + * calculated. Often a radio medium is a tick observer and makes these + * calculations after each tick loop. A radio medium observer may then gather + * network data by being notified every time the radio medium has delivered + * data. The radio medium observable MUST notify observers every time the + * getLastTickConnections returns a new value, even if the new value is null. + * + * @see #getLastTickConnections() + * @see #deleteRadioMediumObserver(Observer) + * @param observer + * New observer + */ + public abstract void addRadioMediumObserver(Observer observer); + + /** + * Deletes an radio medium observer. + * + * @see #addRadioMediumObserver(Observer) + * @param observer + * Observer to delete + */ + public abstract void deleteRadioMediumObserver(Observer observer); + + /** + * Returns all connections made during last tick loop. + * + * Typically a radio medium is a tick observer and transfers data between + * radios after each tick loop. When these calculations are finished, it will + * in turn notify all radio medium observers. A radio medium observer may get + * information about which connections were made by using this method. Observe + * that this method may return null of no connections were made. + * + * @see RadioConnection + * @return All connections made during last tick loop or null if none + */ + public abstract RadioConnection[] getLastTickConnections(); + + /** + * Set an overall connection logger. This logger will see all connections in + * the radio medium. + * + * @param logger + * New overall connection logger. + */ + public abstract void setConnectionLogger(ConnectionLogger logger); + + /** + * Returns XML elements representing the current config of this radio medium. + * This is fetched by the simulator for example when saving a simulation + * configuration file. For example a radio medium may return user altered + * range parameters. This method should however not return state specific + * information such as a current radio status. (All nodes are restarted when + * loading a simulation.) + * + * @see #setConfigXML(Collection) + * @return XML elements representing the current radio medium config + */ + public abstract Collection getConfigXML(); + + /** + * Sets the current radio medium config depending on the given XML elements. + * + * @see #getConfigXML() + * @param configXML + * Config XML elements + * @return True if config was set successfully, false otherwise + */ + public abstract boolean setConfigXML(Collection configXML); + +} diff --git a/tools/cooja/java/se/sics/cooja/SectionMoteMemory.java b/tools/cooja/java/se/sics/cooja/SectionMoteMemory.java new file mode 100644 index 000000000..9f51a708a --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/SectionMoteMemory.java @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: SectionMoteMemory.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.MoteMemory; + +/** + * Represents a mote memory consisting of non-overlapping + * memory sections. This memory also contains information + * about variable starts addresses. + *

+ * When an unhandled memory segment is set a new section is + * automatically created for this segment. + *

+ * @author Fredrik Osterlind + */ +public class SectionMoteMemory implements MoteMemory { + private static Logger logger = Logger.getLogger(SectionMoteMemory.class); + + private Vector sections = new Vector(); + private final Properties variableAddresses; + + /** + * Create a new mote memory with information about which + * variables exist and their relative memory addresses. + * + * @param variableAddresses Variable addresses + */ + public SectionMoteMemory(Properties variableAddresses) { + this.variableAddresses = variableAddresses; + } + + /** + * @return All variable names known and residing in this memory + */ + public String[] getVariableNames() { + String[] names = new String[variableAddresses.size()]; + Enumeration nameEnum = variableAddresses.keys(); + for (int i=0; i < variableAddresses.size(); i++) { + names[i] = (String) nameEnum.nextElement(); + } + return names; + } + + public void clearMemory() { + sections.clear(); + } + + public byte[] getMemorySegment(int address, int size) { + for (int i=0; i < sections.size(); i++) { + if (sections.elementAt(i).includesAddr(address) && sections.elementAt(i).includesAddr(address + size - 1)) { + return sections.elementAt(i).getMemorySegment(address, size); + } + } + return null; + } + + public void setMemorySegment(int address, byte[] data) { + // TODO Creating overlapping memory sections possible + for (int i=0; i < sections.size(); i++) { + if (sections.elementAt(i).includesAddr(address) && sections.elementAt(i).includesAddr(address + data.length - 1)) { + sections.elementAt(i).setMemorySegment(address, data); + return; + } + } + sections.add(new MoteMemorySection(address, data)); + } + + public int getTotalSize() { + int totalSize = 0; + for (MoteMemorySection section: sections) + totalSize += section.getSize(); + return totalSize; + } + + /** + * Returns the total number of sections in this memory. + * + * @return Number of sections + */ + public int getNumberOfSections() { + return sections.size(); + } + + /** + * Removes a memory segment from this memory. + * The section containing the segment may be split into two sections. + * + * @param startAddr Start address + * @param size Length + */ + public void removeSegmentFromMemory(int startAddr, int size) { + for (MoteMemorySection section: sections) + // Find section containing segment to remove + if (section.includesAddr(startAddr) && + section.includesAddr(startAddr + size - 1)) { + MoteMemorySection oldSection = section; + + byte[] dataFirstPart = oldSection.getMemorySegment( + oldSection.startAddr, + (int) (startAddr - oldSection.startAddr) + ); + byte[] dataSecondPart = oldSection.getMemorySegment( + startAddr + size, + (int) (oldSection.startAddr + oldSection.getSize() - (startAddr + size))); + + MoteMemorySection newSectionFirstPart = new MoteMemorySection(oldSection.startAddr, dataFirstPart); + MoteMemorySection newSectionSecondPart = new MoteMemorySection(startAddr + size, dataSecondPart); + + // Remove old section, add new sections + sections.remove(oldSection); + if (newSectionFirstPart.getSize() > 0) + sections.add(newSectionFirstPart); + if (newSectionSecondPart.getSize() > 0) + sections.add(newSectionSecondPart); + } + } + + + /** + * Get start address of section at given position. + * + * @param sectionNr Section position + * @return Start address of section + */ + public int getStartAddrOfSection(int sectionNr) { + if (sectionNr >= sections.size()) + return 0; + + return sections.elementAt(sectionNr).getStartAddr(); + } + + /** + * Get size of section at given position. + * + * @param sectionNr Section position + * @return Size of section + */ + public int getSizeOfSection(int sectionNr) { + if (sectionNr >= sections.size()) + return 0; + + return sections.elementAt(sectionNr).getSize(); + } + + /** + * Get data of section at given position. + * + * @param sectionNr Section position + * @return Data at section + */ + public byte[] getDataOfSection(int sectionNr) { + if (sectionNr >= sections.size()) + return null; + + return sections.elementAt(sectionNr).getData(); + } + + /** + * Returns a value of the integer variable with the given name. + * + * @param varName Name of integer variable + * @return Value of integer variable + */ + public int getIntValueOf(String varName) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return -1; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + byte[] varData = getMemorySegment(varAddr, 4); + + int retVal = 0; + int pos = 0; + retVal += ((int) (varData[pos++] & 0xFF)) << 24; + retVal += ((int) (varData[pos++] & 0xFF)) << 16; + retVal += ((int) (varData[pos++] & 0xFF)) << 8; + retVal += ((int) (varData[pos++] & 0xFF)) << 0; + + // TODO Check if small/big-endian when coming from JNI? + retVal = Integer.reverseBytes(retVal); + + return retVal; + } + + /** + * Set integer value of variable with given name. + * + * @param varName Name of integer variable + * @param newVal New value to set + */ + public void setIntValueOf(String varName, int newVal) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + // TODO Check if small/big-endian when coming from JNI? + int newValToSet = Integer.reverseBytes(newVal); + + // Create byte array + int pos = 0; + + byte[] varData = new byte[4]; + varData[pos++] = (byte) ((newValToSet & 0xFF000000) >> 24); + varData[pos++] = (byte) ((newValToSet & 0xFF0000) >> 16); + varData[pos++] = (byte) ((newValToSet & 0xFF00) >> 8); + varData[pos++] = (byte) ((newValToSet & 0xFF) >> 0); + + setMemorySegment(varAddr, varData); + } + + /** + * Returns a value of the byte variable with the given name. + * + * @param varName Name of byte variable + * @return Value of byte variable + */ + public byte getByteValueOf(String varName) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return -1; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + byte[] varData = getMemorySegment(varAddr, 1); + + return varData[0]; + } + + /** + * Set byte value of variable with given name. + * + * @param varName Name of byte variable + * @param newVal New value to set + */ + public void setByteValueOf(String varName, byte newVal) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + byte[] varData = new byte[1]; + + varData[0] = newVal; + + setMemorySegment(varAddr, varData); + } + + /** + * Returns byte array of given length and with the given name. + * + * @param varName Name of array + * @param length Length of array + * @return Data of array + */ + public byte[] getByteArray(String varName, int length) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return null; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + // TODO Check if small/big-endian when coming from JNI? + return getMemorySegment(varAddr, length); + } + + /** + * Set byte array of the variable with the given name. + * + * @param varName Name of array + * @param data New data of array + */ + public void setByteArray(String varName, byte[] data) { + // Get start address of variable + if (!variableAddresses.containsKey(varName)) + return; + int varAddr = ((Integer) variableAddresses.get(varName)).intValue(); + + // TODO Check if small/big-endian when coming from JNI? + setMemorySegment(varAddr, data); + } + + /** + * A memory section represented of a byte array and a start address. + * + * @author Fredrik Osterlind + */ + private class MoteMemorySection { + private byte[] data = null; + private int startAddr; + + /** + * Create a new memory section. + * + * @param startAddr Start address of section + * @param data Data of section + */ + public MoteMemorySection(int startAddr, byte[] data) { + this.startAddr = startAddr; + this.data = data; + } + + /** + * Returns start address of this memory section. + * + * @return Start address + */ + public int getStartAddr() { + return startAddr; + } + + /** + * Returns size of this memory section. + * + * @return Size + */ + public int getSize() { + return data.length; + } + + /** + * Returns the entire byte array which defines this section. + * + * @return Byte array + */ + public byte[] getData() { + return data; + } + + /** + * True if given address is part of this memory section. + * + * @param addr Address + * @return True if given address is part of this memory section, false otherwise + */ + public boolean includesAddr(int addr) { + if (data == null) + return false; + + return (addr >= startAddr && addr < (startAddr + data.length)); + } + + /** + * Returns memory segment. + * + * @param addr Start address of memory segment + * @param size Size of memory segment + * @return Memory segment + */ + public byte[] getMemorySegment(int addr, int size) { + byte[] ret = new byte[size]; + for (int i = 0; i < size; i++) + ret[i] = data[(int) (addr - startAddr + i)]; + return ret; + } + + /** + * Sets a memory segment. + * @param addr Start of memory segment + * @param data Data of memory segment + */ + public void setMemorySegment(int addr, byte[] data) { + int nonnull=0; + for (int i = 0; i < data.length; i++) { + if (data[i] != 0) nonnull++; + this.data[(int) (addr - startAddr + i)] = data[i]; + } + } + + public MoteMemorySection clone() { + byte[] dataClone = new byte[data.length]; + for (int i=0; i < data.length; i++) + dataClone[i] = data[i]; + + MoteMemorySection clone = new MoteMemorySection(startAddr, dataClone); + return clone; + } + + } + + + // EXPERIMENTAL AND DEBUG METHODS + public SectionMoteMemory clone() { + Vector clonedSections = new Vector(); + for (int i=0; i < sections.size(); i++) { + clonedSections.add((MoteMemorySection) sections.elementAt(i).clone()); + } + + SectionMoteMemory clone = new SectionMoteMemory(variableAddresses); + clone.sections = clonedSections; + + return clone; + } + + protected byte[] getChecksum() { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance("MD5"); + + for (int i=0; i < sections.size(); i++) { + messageDigest.update(sections.get(i).data, 0, sections.get(i).getSize()); + } + } catch (NoSuchAlgorithmException e) { + return null; + } + return messageDigest.digest(); + } + + protected Vector getDifferenceAddressesOf(SectionMoteMemory anotherMem) { + Vector differences = new Vector(); + + if (this.getNumberOfSections() != anotherMem.getNumberOfSections()) { + logger.fatal("Number of section do not match!"); + return null; + } + + for (int i=0; i < sections.size(); i++) { + if (this.getSizeOfSection(i) != anotherMem.getSizeOfSection(i)) { + logger.fatal("Section " + i + " sizes do not match!"); + return null; + } + if (this.getStartAddrOfSection(i) != anotherMem.getStartAddrOfSection(i)) { + logger.fatal("Section " + i + " start addresses do not match!"); + return null; + } + + for (int j=0; j < sections.get(i).getSize(); j++) + if (this.sections.get(i).data[j] != anotherMem.getDataOfSection(i)[j]) + differences.add(new Integer(sections.get(i).startAddr + j)); + System.err.println(); + } + return differences; + } + + protected void printMemory() { + for (int i=0; i < sections.size(); i++) { + System.err.print("Section[" + i + "]: "); + for (int j=0; j < sections.get(i).getSize(); j++) + System.err.print(sections.get(i).getData()[j] + ","); + System.err.println(); + } + } + + protected void printDifferences(SectionMoteMemory anotherMem) { + if (this.getNumberOfSections() != anotherMem.getNumberOfSections()) { + logger.fatal("Number of section do not match!"); + return; + } + + for (int i=0; i < sections.size(); i++) { + if (this.getSizeOfSection(i) != anotherMem.getSizeOfSection(i)) { + logger.fatal("Section " + i + " sizes do not match!"); + return; + } + if (this.getStartAddrOfSection(i) != anotherMem.getStartAddrOfSection(i)) { + logger.fatal("Section " + i + " start addresses do not match!"); + return; + } + + System.err.print("Section[" + i + "]: "); + for (int j=0; j < sections.get(i).getSize(); j++) + if (this.sections.get(i).data[j] != anotherMem.getDataOfSection(i)[j]) + System.err.print(j + ","); + System.err.println(); + } + } + + protected void printChecksum() { + byte[] checksum = this.getChecksum(); + System.err.print("Checksum: "); + for (int i=0; i < checksum.length; i++) { + System.err.print(checksum[i] + ","); + } + System.err.println(); + } + + + +} diff --git a/tools/cooja/java/se/sics/cooja/Simulation.java b/tools/cooja/java/se/sics/cooja/Simulation.java new file mode 100644 index 000000000..da42c58fe --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/Simulation.java @@ -0,0 +1,735 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Simulation.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.io.*; +import java.util.*; + +import org.apache.log4j.Logger; +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; + +import se.sics.cooja.dialogs.*; + +/** + * A simulation contains motes and ticks them one by one. When all motes has + * been ticked once, the simulation sleeps for some specified time, and the + * current simulation time is updated. Some observers (tick observers) are also + * notified. + * + * When observing the simulation itself, the simulation state, added or deleted + * motes etc are observed, as opposed to individual mote changes. Changes of + * individual motes should instead be observed via corresponding mote + * interfaces. + * + * @author Fredrik Osterlind + */ +public class Simulation extends Observable implements Runnable { + + private Vector motes = new Vector(); + private Vector moteTypes = new Vector(); + + private int delayTime = 100; + private int currentSimulationTime = 0; + private int tickTime = 1; + private String title = null; + + // Radio Medium + private RadioMedium currentRadioMedium = null; + + private static Logger logger = Logger.getLogger(Simulation.class); + + private boolean isRunning = false; + private boolean stopSimulation = false; + private Thread thread = null; + + // Tick observable + private class TickObservable extends Observable { + private void allTicksPerformed() { + setChanged(); + notifyObservers(); + } + } + private TickObservable tickObservable = new TickObservable(); + + /** + * Add tick observer. This observer is notified once every tick loop, that is, + * when all motes have been ticked. + * + * @see #deleteTickObserver(Observer) + * @param newObserver + * New observer + */ + public void addTickObserver(Observer newObserver) { + tickObservable.addObserver(newObserver); + } + + /** + * Delete an existing tick observer. + * + * @see #addTickObserver(Observer) + * @param observer + * Observer to delete + */ + public void deleteTickObserver(Observer observer) { + tickObservable.deleteObserver(observer); + } + + public void run() { + long lastStartTime = System.currentTimeMillis(); + logger.info("Simulation main loop started, system time: " + lastStartTime); + isRunning = true; + + // Notify observers simulation is starting + this.setChanged(); + this.notifyObservers(this); + + while (isRunning) { + try { + // Tick all motes + for (Mote moteToTick : motes) { + moteToTick.tick(currentSimulationTime); + } + + // Increase simulation time + currentSimulationTime += tickTime; + + // Notify tick observers + tickObservable.allTicksPerformed(); + + // Sleep + if (delayTime > 0) + Thread.sleep(delayTime); + + if (stopSimulation) { + // We should only tick once (and we just did), so abort now + stopSimulation = false; + isRunning = false; + thread = null; + } + + } catch (InterruptedException e) { + isRunning = false; + thread = null; + break; + } catch (IllegalArgumentException e) { + logger.warn("llegalArgumentException:" + e); + isRunning = false; + thread = null; + break; + } catch (IllegalMonitorStateException e) { + logger.warn("IllegalMonitorStateException:" + e); + isRunning = false; + thread = null; + break; + } + } + + isRunning = false; + thread = null; + stopSimulation = false; + + // Notify observers simulation has stopped + this.setChanged(); + this.notifyObservers(this); + + logger.info("Simulation main loop stopped, system time: " + + System.currentTimeMillis() + "\tDuration: " + + (System.currentTimeMillis() - lastStartTime) + " ms"); + } + + /** + * Creates a new simulation with a delay time of 1 second. + */ + public Simulation() { + // New simulation instance + + // logger.fatal("WARNING ADDING 10000 BLINKER NODES NOW!!!"); + // for (int i=0; i < 10000; i++) + // addMote(new BlinkerNode()); + + } + + /** + * Starts this simulation (notifies observers). + */ + public void startSimulation() { + if (!isRunning()) { + thread = new Thread(this); + thread.start(); + } + } + + /** + * Stops this simulation (notifies observers). + */ + public void stopSimulation() { + if (isRunning()) { + stopSimulation = true; + thread.interrupt(); + + // Wait until simulation stops + if (Thread.currentThread() != thread) + while (thread != null && thread.isAlive()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + } // else logger.fatal("Could not stop simulation: isRunning=" + isRunning + + // ", thread=" + thread); + } + + /** + * Starts simulation if stopped, ticks all motes once, and finally stop + * simulation again. + */ + public void tickSimulation() { + stopSimulation = true; + + if (!isRunning()) { + thread = new Thread(this); + thread.start(); + } + + // Wait until simulation stops + while (thread != null && thread.isAlive()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + } + } + + } + + /** + * Loads a simulation configuration from given file. + * + * When loading mote types, user must recompile the actual library of each + * type. User may also change mote type settings at this point. + * + * @see #saveSimulationConfig(File) + * @param file + * File to read + * @return New simulation or null if recompiling failed or aborted + * @throws UnsatisfiedLinkError + * If associated libraries could not be loaded + */ + public static Simulation loadSimulationConfig(File file) + throws UnsatisfiedLinkError { + + Simulation newSim = null; + + try { + // Open config file + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(file); + Element root = doc.getRootElement(); + + // Check that config file version is correct + if (!root.getName().equals("simulation")) { + logger.fatal("Not a COOJA simulation config xml file!"); + return null; + } + + // Build new simulation + Collection config = root.getChildren(); + newSim = new Simulation(); + boolean createdOK = newSim.setConfigXML(config); + if (!createdOK) { + logger.info("Simulation not loaded"); + return null; + } + + } catch (JDOMException e) { + logger.fatal("File not wellformed: " + e.getMessage()); + return null; + } catch (IOException e) { + logger.fatal("No access to file: " + e.getMessage()); + return null; + } catch (Exception e) { + logger.fatal("Exception when loading file: " + e); + e.printStackTrace(); + return null; + } + + return newSim; + } + + /** + * Saves current simulation configuration to given file and notifies + * observers. + * + * @see #loadSimulationConfig(File file) + * @see #getConfigXML() + * @param file + * File to write + */ + public void saveSimulationConfig(File file) { + + try { + // Create simulation XML + Element root = new Element("simulation"); + root.addContent(getConfigXML()); + + // Create config + Document doc = new Document(root); + + // Write to file + FileOutputStream out = new FileOutputStream(file); + XMLOutputter outputter = new XMLOutputter(); + outputter.setFormat(Format.getPrettyFormat()); + outputter.output(doc, out); + + out.close(); + + logger.info("Saved to file: " + file.getAbsolutePath()); + } catch (Exception e) { + logger.warn("Exception while saving simulation config: " + e); + } + + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Returns the current simulation config represented by XML elements. This + * config also includes the current radio medium, all mote types and motes. + * + * @see #saveSimulationConfig(File file) + * @return Current simulation config + */ + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Title + element = new Element("title"); + element.setText(title); + config.add(element); + + // Delay time + element = new Element("delaytime"); + element.setText(Integer.toString(delayTime)); + config.add(element); + + // Simulation time + element = new Element("simtime"); + element.setText(Integer.toString(currentSimulationTime)); + config.add(element); + + // Tick time + element = new Element("ticktime"); + element.setText(Integer.toString(tickTime)); + config.add(element); + + // Radio Medium + element = new Element("radiomedium"); + element.setText(currentRadioMedium.getClass().getName()); + + Collection radioMediumXML = currentRadioMedium.getConfigXML(); + if (radioMediumXML != null) + element.addContent(radioMediumXML); + config.add(element); + + // Mote types + for (MoteType moteType : getMoteTypes()) { + element = new Element("motetype"); + element.setText(moteType.getClass().getName()); + + Collection moteTypeXML = moteType.getConfigXML(); + if (moteTypeXML != null) + element.addContent(moteTypeXML); + config.add(element); + } + + // Motes + for (Mote mote : motes) { + element = new Element("mote"); + element.setText(mote.getClass().getName()); + + Collection moteXML = mote.getConfigXML(); + if (moteXML != null) + element.addContent(moteXML); + config.add(element); + } + + return config; + } + + /** + * Sets the current simulation config depending on the given XML elements. + * + * @see #getConfigXML() + * @param configXML + * Config XML elements + */ + public boolean setConfigXML(Collection configXML) throws Exception { + + // Parse elements + for (Element element : configXML) { + + // Title + if (element.getName().equals("title")) { + title = element.getText(); + } + + // Delay time + if (element.getName().equals("delaytime")) { + delayTime = Integer.parseInt(element.getText()); + } + + // Simulation time + if (element.getName().equals("simtime")) { + currentSimulationTime = Integer.parseInt(element.getText()); + } + + // Tick time + if (element.getName().equals("ticktime")) { + tickTime = Integer.parseInt(element.getText()); + } + + // Radio medium + if (element.getName().equals("radiomedium")) { + String radioMediumClassName = element.getText().trim(); + Class radioMediumClass = GUI.currentGUI + .tryLoadClass(this, RadioMedium.class, radioMediumClassName); + + if (radioMediumClass != null) + // Create radio medium specified in config + currentRadioMedium = radioMediumClass.newInstance(); + else + logger.warn("Could not find radio medium class: " + + radioMediumClassName); + + // Show configure simulation dialog + boolean createdOK = CreateSimDialog.showDialog(GUI.frame, this); + + if (!createdOK) { + logger.debug("Simulation not created, aborting"); + throw new Exception("Load aborted by user"); + } + + // Check if radio medium specific config should be applied + if (radioMediumClassName + .equals(currentRadioMedium.getClass().getName())) { + currentRadioMedium.setConfigXML(element.getChildren()); + } else { + logger + .info("Radio Medium changed - ignoring radio medium specific config"); + } + } + + // Mote type + if (element.getName().equals("motetype")) { + String moteTypeClassName = element.getText().trim(); + + Class moteTypeClass = GUI.currentGUI.tryLoadClass( + this, MoteType.class, moteTypeClassName); + + if (moteTypeClass == null) { + logger.fatal("Could not load mote type class: " + moteTypeClassName); + return false; + } + + MoteType moteType = moteTypeClass.getConstructor((Class[]) null) + .newInstance(); + + boolean createdOK = moteType.setConfigXML(this, element.getChildren()); + if (createdOK) { + addMoteType(moteType); + } else { + logger + .fatal("Mote type was not created: " + element.getText().trim()); + throw new Exception("All mote types were not recreated"); + } + } + + // Mote + if (element.getName().equals("mote")) { + Class moteClass = GUI.currentGUI.tryLoadClass(this, + Mote.class, element.getText().trim()); + + Mote mote = moteClass.getConstructor((Class[]) null).newInstance(); + if (mote.setConfigXML(this, element.getChildren())) { + addMote(mote); + } else { + logger.fatal("Mote was not created: " + element.getText().trim()); + throw new Exception("All motes were not recreated"); + } + } + } + + return true; + } + + /** + * Removes a mote from this simulation + * + * @param mote + * Mote to remove + */ + public void removeMote(Mote mote) { + if (isRunning()) { + stopSimulation(); + motes.remove(mote); + startSimulation(); + } else + motes.remove(mote); + + currentRadioMedium.unregisterMote(mote, this); + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Adds a mote to this simulation + * + * @param mote + * Mote to add + */ + public void addMote(Mote mote) { + if (isRunning()) { + stopSimulation(); + motes.add(mote); + startSimulation(); + } else + motes.add(mote); + + currentRadioMedium.registerMote(mote, this); + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Get a mote from this simulation. + * + * @param pos + * Position of mote + * @return Mote + */ + public Mote getMote(int pos) { + return motes.get(pos); + } + + /** + * Returns number of motes in this simulation. + * + * @return Number of motes + */ + public int getMotesCount() { + return motes.size(); + } + + /** + * Returns all mote types in simulation. + * + * @return All mote types + */ + public Vector getMoteTypes() { + return moteTypes; + } + + /** + * Returns mote type with given identifier. + * + * @param identifier + * Mote type identifier + * @return Mote type or null if not found + */ + public MoteType getMoteType(String identifier) { + for (MoteType moteType : getMoteTypes()) { + if (moteType.getIdentifier().equals(identifier)) + return moteType; + } + return null; + } + + /** + * Adds given mote type to simulation. + * + * @param newMoteType + */ + public void addMoteType(MoteType newMoteType) { + moteTypes.add(newMoteType); + + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Set delay time to delayTime. When all motes have been ticked, the + * simulation waits for this time before ticking again. + * + * @param delayTime + * New delay time (ms) + */ + public void setDelayTime(int delayTime) { + this.delayTime = delayTime; + + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Returns current delay time. + * + * @return Delay time (ms) + */ + public int getDelayTime() { + return delayTime; + } + + /** + * Set simulation time to simulationTime. + * + * @param simulationTime + * New simulation time (ms) + */ + public void setSimulationTime(int simulationTime) { + currentSimulationTime = simulationTime; + + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Returns current simulation time. + * + * @return Simulation time (ms) + */ + public int getSimulationTime() { + return currentSimulationTime; + } + + /** + * Set tick time to tickTime. The tick time is the simulated time every tick + * takes. When all motes have been ticked, current simulation time is + * increased with tickTime. Default tick time is 1 ms. + * + * @see #getTickTime() + * @see #getTickTimeInSeconds() + * @param tickTime + * New tick time (ms) + */ + public void setTickTime(int tickTime) { + this.tickTime = tickTime; + + this.setChanged(); + this.notifyObservers(this); + } + + /** + * Changes radio medium of this simulation to the given. + * + * @param radioMedium + * New radio medium + */ + public void setRadioMedium(RadioMedium radioMedium) { + // Remove current radio medium from observing motes + if (currentRadioMedium != null) + for (int i = 0; i < motes.size(); i++) + currentRadioMedium.unregisterMote(motes.get(i), this); + + // Change current radio medium to new one + if (radioMedium == null) { + logger.fatal("Radio medium could not be created!"); + return; + } + this.currentRadioMedium = radioMedium; + + // Add all current motes to be observered by new radio medium + for (int i = 0; i < motes.size(); i++) + currentRadioMedium.registerMote(motes.get(i), this); + } + + /** + * Get currently used radio medium. + * + * @return Currently used radio medium + */ + public RadioMedium getRadioMedium() { + return currentRadioMedium; + } + + /** + * Get current tick time (ms). + * + * @see #setTickTime(int) + * @return Current tick time (ms) + */ + public int getTickTime() { + return tickTime; + } + + /** + * Get current tick time (seconds). + * + * @see #setTickTime(int) + * @return Current tick time (seconds) + */ + public double getTickTimeInSeconds() { + return ((double) tickTime) / 1000.0; + } + + /** + * Return true is simulation is running. + * + * @return True if simulation is running + */ + public boolean isRunning() { + return isRunning && thread != null; + } + + /** + * Get current simulation title (short description). + * + * @return Title + */ + public String getTitle() { + return title; + } + + /** + * Set simulation title. + * + * @param title + * New title + */ + public void setTitle(String title) { + this.title = title; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/VisPlugin.java b/tools/cooja/java/se/sics/cooja/VisPlugin.java new file mode 100644 index 000000000..3afc48ccc --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/VisPlugin.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisPlugin.java,v 1.1 2006/08/21 12:12:56 fros4943 Exp $ + */ + +package se.sics.cooja; + +import javax.swing.JInternalFrame; +import javax.swing.event.InternalFrameEvent; +import javax.swing.event.InternalFrameListener; + +/** + * Abstract class VisPlugin should be implemented by plugins for COOJA Simulator. + * By extending JInternalFrame, the visual apperence is decided by the plugin itself. + * + * An implemented plugin should be registered at runtime using the following method: + * GUI.registerPlugin(Class, String) + * + * For example how to implement a plugin see classes SimControl or Vis2D. + * + * @author Fredrik Osterlind + */ +public abstract class VisPlugin extends JInternalFrame { + + /** + * Sets frame title + * @param title Frame title + */ + public VisPlugin(String title) { + super(title, true, true, true, true); + final VisPlugin thisPlugin = this; + + // Close via gui + setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + // Detect frame events + addInternalFrameListener(new InternalFrameListener() { + public void internalFrameClosing(InternalFrameEvent e) { + GUI.currentGUI.removePlugin(thisPlugin, true); + } + public void internalFrameClosed(InternalFrameEvent e) { + // NOP + } + public void internalFrameOpened(InternalFrameEvent e) { + // NOP + } + public void internalFrameIconified(InternalFrameEvent e) { + // NOP + } + public void internalFrameDeiconified(InternalFrameEvent e) { + // NOP + } + public void internalFrameActivated(InternalFrameEvent e) { + // NOP + } + public void internalFrameDeactivated(InternalFrameEvent e) { + // NOP + } + } + ); + } + + /** + * This method is called when an opened plugin is about to close. + * It should release any resources such as registered observers or + * opened interface visualizers. + */ + public abstract void closePlugin(); + +} diff --git a/tools/cooja/java/se/sics/cooja/VisPluginType.java b/tools/cooja/java/se/sics/cooja/VisPluginType.java new file mode 100644 index 000000000..e30f62519 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/VisPluginType.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisPluginType.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation type to describe a plugin type. + * + * @author Fredrik Osterlind + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface VisPluginType { + public static final int UNDEFINED_PLUGIN = 0; + + /** + * Mote Plugin + * + * A mote plugin concerns one specific mote. + * + * An example of such a plugin may be to display + * some mote information in a frame. + * + * Mote plugins can not be instantiated from the + * regular menu bar, but are instead started from + * other plugins, for example a visualizer that let's + * a user select a mote. + * + * When constructed, a mote plugin is given a Mote. + * + * If the current simulation is removed, so are + * all instances of this plugin. + */ + public static final int MOTE_PLUGIN = 1; + + /** + * Simulation Plugin + * + * A simulation plugin concerns one specific simulation. + * + * An example of such a plugin may be to display + * number of motes and current simulation time in a window. + * + * Simulation plugins are available via the plugins menubar. + * + * When constructed, a simulation plugin is given the current + * active simulation. + * + * If the current simulation is removed, so are + * all instances of this plugin. + */ + public static final int SIM_PLUGIN = 2; + + /** + * GUI Plugin + * + * A GUI plugin does not depend on the current simulation to function. + * + * An example of such a plugin may be a control panel + * where a user can control the current simulation. + * + * GUI plugins are available via the plugins menubar. + * + * When constructed, a GUI plugin is given the current GUI. + */ + public static final int GUI_PLUGIN = 3; + + /** + * Simulation Standard Plugin + * + * This is treated exactly like a Simulation Plugin, with the + * only difference that this will automatically be opened + * when a new simulation is created. + */ + public static final int SIM_STANDARD_PLUGIN = 4; + + int value(); +} + diff --git a/tools/cooja/java/se/sics/cooja/contikimote/ContikiMote.java b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMote.java new file mode 100644 index 000000000..0c2edb5ae --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMote.java @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiMote.java,v 1.1 2006/08/21 12:13:10 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote; + +import java.util.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * A Contiki mote executes an actual Contiki system via + * a loaded shared library and JNI. + * It contains a section mote memory, a mote interface handler and a + * Contiki mote type. + * + * The mote type is responsible for the connection to the loaded + * Contiki system. + * + * When ticked a Contiki mote polls all interfaces, copies the mote + * memory to the core, lets the Contiki system handle one event, + * fetches the updated memory and finally polls all interfaces again. + * The mote state is also updated during a mote tick. + * + * @author Fredrik Osterlind + */ +public class ContikiMote implements Mote { + private static Logger logger = Logger.getLogger(ContikiMote.class); + + private ContikiMoteType myType = null; + private SectionMoteMemory myMemory = null; + private MoteInterfaceHandler myInterfaceHandler = null; + private Simulation mySim = null; + + // Time to wake up if sleeping + private int wakeUpTime = 0; + + private int myState = STATE_ACTIVE; + + // State observable + private class StateObservable extends Observable { + private void stateChanged() { + setChanged(); + notifyObservers(); + } + } + private StateObservable stateObservable = new StateObservable(); + + + /** + * Creates a new uninitialized Contiki mote. + * + * This mote needs at least a type, a memory, a mote interface handler + * and to be connected to a simulation. + */ + public ContikiMote() { + } + + /** + * Creates a new mote of given type. + * Both the initial mote memory and the interface handler + * are supplied from the mote type. + * + * @param moteType Mote type + * @param sim Mote's simulation + */ + public ContikiMote(ContikiMoteType moteType, Simulation sim) { + this.mySim = sim; + this.myType = moteType; + this.myMemory = moteType.createInitialMemory(); + this.myInterfaceHandler = new MoteInterfaceHandler((Mote) this, moteType.getMoteInterfaces()); + + myState = STATE_ACTIVE; + } + + public void setState(int newState) { + if (myState == STATE_DEAD) { + return; + } + + if (myState == STATE_ACTIVE && newState != STATE_ACTIVE) { + myState = newState; + stateObservable.stateChanged(); + } + + if (myState == STATE_LPM && newState != STATE_LPM) { + myState = newState; + stateObservable.stateChanged(); + } + } + + public int getState() { + return myState; + } + + public void addStateObserver(Observer newObserver) { + stateObservable.addObserver(newObserver); + } + + public void deleteStateObserver(Observer newObserver) { + stateObservable.deleteObserver(newObserver); + } + + public MoteInterfaceHandler getInterfaces() { + return myInterfaceHandler; + } + + public void setInterfaces(MoteInterfaceHandler newInterfaces) { + myInterfaceHandler = newInterfaces; + } + + public MoteMemory getMemory() { + return myMemory; + } + + public void setMemory(MoteMemory memory) { + myMemory = (SectionMoteMemory) memory; + } + + public MoteType getType() { + return myType; + } + + public void setType(MoteType type) { + myType = (ContikiMoteType) type; + } + + public Simulation getSimulation() { + return mySim; + } + + public void setSimulation(Simulation simulation) { + mySim = simulation; + } + + /** + * Ticks this mote once. This is done by first polling all interfaces + * and letting them act on the stored memory before the memory is set. Then + * the mote is ticked, and the new memory is received. + * Finally all interfaces are allowing to act on the new memory in order to + * discover relevant changes. This method also checks if mote should go to sleep + * depending on Contiki specifics; pending timers and polled processes. + * + * @param simTime Current simulation time + */ + public void tick(int simTime) { + + // If mote is dead, do nothing at all + if (getState() == STATE_DEAD) + return; + + // If mote is sleeping and has a wake up time, should it wake up now? + if (getState() == STATE_LPM && wakeUpTime > 0 && wakeUpTime <= simTime) { + setState(STATE_ACTIVE); + wakeUpTime = 0; + } + + // If mote is active.. + if (getState() == STATE_ACTIVE) { + // Let all active interfaces act before tick + // Observe that each interface may put the mote to sleep at this point + myInterfaceHandler.doActiveActionsBeforeTick(); + } + + // And let passive interfaces act even if mote is sleeping + myInterfaceHandler.doPassiveActionsBeforeTick(); + + + // If mote is still active, complete this tick + if (getState() == STATE_ACTIVE) { + + // Copy mote memory to core + myType.setCoreMemory(myMemory); + + // Tick node + myType.tick(); + + // Fetch new updated memory from core + myType.getCoreMemory(myMemory); + + // Let all active interfaces act again after tick + myInterfaceHandler.doActiveActionsAfterTick(); + + } + + // Finally let all passive interfaces act + myInterfaceHandler.doPassiveActionsAfterTick(); + + // If mote is awake, should it go to sleep? + if (getState() == STATE_ACTIVE) { + // Check if this mote should sleep (no more pending timers or processes to poll) + int processRunValue = myMemory.getIntValueOf("simProcessRunValue"); + int etimersPending = myMemory.getIntValueOf("simEtimerPending"); + int nextExpirationTime = myMemory.getIntValueOf("simNextExpirationTime"); + + if (processRunValue == 0 && etimersPending == 0) { + setState(STATE_LPM); + wakeUpTime = 0; + } + + if (processRunValue == 0 && etimersPending == 1 && nextExpirationTime > 0) { + setState(STATE_LPM); + wakeUpTime = nextExpirationTime; + } + + } + } + + /** + * Returns the current Contiki mote config represented by XML elements. + * This config also includes all mote interface configs. + * + * @return Current simulation config + */ + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Mote type identifier + element = new Element("motetype_identifier"); + element.setText(getType().getIdentifier()); + config.add(element); + + // Active interface configs (if any) + for (MoteInterface moteInterface: getInterfaces().getAllActiveInterfaces()) { + element = new Element("interface_config"); + element.setText(moteInterface.getClass().getName()); + + Collection interfaceXML = moteInterface.getConfigXML(); + if (interfaceXML != null) { + element.addContent(interfaceXML); + config.add(element); + } + } + + // Passive interface configs (if any) + for (MoteInterface moteInterface: getInterfaces().getAllPassiveInterfaces()) { + element = new Element("interface_config"); + element.setText(moteInterface.getClass().getName()); + + Collection interfaceXML = moteInterface.getConfigXML(); + if (interfaceXML != null) { + element.addContent(interfaceXML); + config.add(element); + } + } + + return config; + } + + public boolean setConfigXML(Simulation simulation, Collection configXML) { + mySim = simulation; + myState = STATE_ACTIVE; + + for (Element element: configXML) { + String name = element.getName(); + + if (name.equals("motetype_identifier")) { + myType = (ContikiMoteType) simulation.getMoteType(element.getText()); + myMemory = myType.createInitialMemory(); + myInterfaceHandler = new MoteInterfaceHandler((Mote) this, myType.getMoteInterfaces()); + + } else if (name.equals("interface_config")) { + Class moteInterfaceClass = + GUI.currentGUI.tryLoadClass(this, MoteInterface.class, element.getText().trim()); + + if (moteInterfaceClass == null) { + logger.fatal("Could not load mote interface class: " + element.getText().trim()); + return false; + } + + MoteInterface moteInterface = myInterfaceHandler.getInterfaceOfType(moteInterfaceClass); + moteInterface.setConfigXML(element.getChildren()); + } + } + + return true; + } + + public String toString() { + if (getInterfaces().getMoteID() != null) { + return "Contiki Mote, ID=" + getInterfaces().getMoteID().getMoteID(); + } else + return "Contiki Mote, ID=null"; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteInterface.java b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteInterface.java new file mode 100644 index 000000000..71ecc13e4 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteInterface.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiMoteInterface.java,v 1.1 2006/08/21 12:13:09 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote; + +/** + * A Contiki mote interface has information about which, if any, core interfaces + * it needs. + * + * All classes implementing this interface should also define a static method: + * public String[] getCoreInterfaceDependencies() { + * ... + * } + * + * The method should return the names of all needed core interfaces. + * + * @author Fredrik Osterlind + */ +public interface ContikiMoteInterface { +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteType.java b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteType.java new file mode 100644 index 000000000..74798d798 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteType.java @@ -0,0 +1,898 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiMoteType.java,v 1.1 2006/08/21 12:13:09 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.io.*; +import java.security.*; +import java.util.*; +import java.util.regex.*; +import javax.swing.*; + +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * The Contiki mote type holds the native library used to communicate with an + * underlying Contiki system. All communication with that system should always + * pass through this mote type. + *

+ * This type also contains information about which processes, sensors and core + * interfaces a mote of this type has, as well as where the Contiki OS, COOJA + * core files and an optional user platform are located. + *

+ * All core communication with the Contiki mote should be via this class. When a + * mote type is created it allocates a CoreComm to be used with this type, and + * loads a map file. The map file is used to map variable names to addresses. + *

+ * When a new mote type is created an initialization function is run on the + * Contiki system in order to create the initial memory. When a new mote is + * created the createInitialMemory() method should be called to get this initial + * memory for the mote. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Contiki Mote Type") +public class ContikiMoteType implements MoteType { + private static Logger logger = Logger.getLogger(ContikiMoteType.class); + + /** + * Map file suffix + */ + final static public String mapSuffix = ".map"; + + /** + * Library file suffix + */ + final static public String librarySuffix = ".library"; + + /** + * Make dependency file suffix + */ + final static public String dependSuffix = ".a"; + + /** + * Temporary output directory + */ + final static public File tempOutputDirectory = new File("." + File.separatorChar + "obj_cooja" + File.separatorChar); + + // Regular expressions for parsing the map file + final static private String bssSectionAddrRegExp = "^.bss[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String bssSectionSizeRegExp = "^.bss[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String dataSectionAddrRegExp = "^.data[ \t]*0x([0-9A-Fa-f]*)[ \t]*0x[0-9A-Fa-f]*[ \t]*$"; + final static private String dataSectionSizeRegExp = "^.data[ \t]*0x[0-9A-Fa-f]*[ \t]*0x([0-9A-Fa-f]*)[ \t]*$"; + final static private String varAddressRegExpPrefix = "^[ \t]*0x([0-9A-Fa-f]*)[ \t]*"; + final static private String varAddressRegExpSuffix = "[ \t]*$"; + final static private String varNameRegExp = "^[ \t]*(0x[0-9A-Fa-f]*)[ \t]*([^ ]*)[ \t]*$"; + final static private String varSizeRegExpPrefix = "^"; + final static private String varSizeRegExpSuffix = "[ \t]*(0x[0-9A-Fa-f]*)[ \t]*[^ ]*[ \t]*$"; + + // Mote type specific data + private String identifier = null; + private String description = null; + private String contikiBaseDir = null; + private String contikiCoreDir = null; + private Vector userPlatformDirs = null; + private Vector processes = null; + private Vector sensors = null; + private Vector coreInterfaces = null; + private Vector> moteInterfaces = null; + + // Simulation holding this mote type + private Simulation mySimulation = null; + + // Type specific class configuration + private PlatformConfig myConfig = null; + + // Core communication variables + private String libraryClassName = null; + private int offsetRelToAbs = 0; + private CoreComm myCoreComm = null; + + // Variable name to address mappings + private Properties varAddresses = new Properties(); + + // Map file contents + private Vector mapContents = new Vector(); + + // Initial memory for all motes of this type + private SectionMoteMemory initialMemory = null; + + /** + * Creates a new uninitialized Contiki mote type. This mote type needs to load + * a library file and parse a map file before it can be used. + */ + public ContikiMoteType() { + } + + /** + * Creates a new Contiki mote type. This type uses two external files: a map + * file for parsing relative addresses of Contiki variables (identifier + + * ".map") and a library file with an actual compiled Contiki system + * (identifier + ".library") + * + * @param identifier + * Unique identifier for this mote type + */ + public ContikiMoteType(String identifier) { + doInit(identifier); + } + + public Mote generateMote(Simulation simulation) { + return new ContikiMote(this, simulation); + } + + public boolean configureAndInit(JFrame parentFrame, Simulation simulation) { + return ContikiMoteTypeDialog.showDialog(parentFrame, simulation, this); + } + + /** + * This is an mote type initialization method and should normally never be + * called by any other part than the mote type constructor. It is called from + * the constructor with an identifier argument. but not from the standard + * constructor. This method may be called from the simulator when loading + * configuration files, and the libraries must be recompiled. + * + * This method allocates a core communicator, loads the Contiki library file, + * loads and parses the map file, creates a variable name to address mapping + * of the Contiki system and finally creates the Contiki mote initial memory. + * + * @param identifier + * Mote type identifier + * @return True if initialization ok, false otherwise + */ + protected boolean doInit(String identifier) { + this.identifier = identifier; + + if (myCoreComm != null) { + logger + .fatal("Core communicator not null. Is library already loaded? Aborting"); + return false; + } + + File libFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + librarySuffix); + File mapFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + mapSuffix); + + // Check that library file exists + if (!libFile.exists()) { + logger.fatal("Library file could not be found: " + libFile); + return false; + } + + // Check that map file exists + if (!mapFile.exists()) { + logger.fatal("Map file could not be found: " + mapFile); + return false; + } + + // Allocate core communicator class + libraryClassName = CoreComm.getAvailableClassName(); + myCoreComm = CoreComm.createCoreComm(libraryClassName, libFile); + + // Load map file + mapContents = loadMapFile(mapFile); + if (mapContents == null) { + logger.fatal("Map file could not be loaded: " + mapFile); + return false; + } + + // Load variable addresses from Contiki system + varAddresses.clear(); + Vector varNames = getAllVariableNames(); + for (String varName : varNames) { + int varAddress = getRelVarAddress(varName); + if (varAddress > 0) { + varAddresses.put(varName, new Integer(varAddress)); + } else + logger.warn("Parsed Contiki variable '" + varName + + "' but could not find address"); + } + + // Get offset between relative and absolute addresses + offsetRelToAbs = getReferenceAbsAddr() + - getRelVarAddr(mapContents, "referenceVar"); + + // Parse addresses of data and BSS memory sections + int relDataSectionAddr = loadRelDataSectionAddr(mapContents); + int dataSectionSize = loadDataSectionSize(mapContents); + int relBssSectionAddr = loadRelBssSectionAddr(mapContents); + int bssSectionSize = loadBssSectionSize(mapContents); + + if (relDataSectionAddr <= 0 || dataSectionSize <= 0 + || relBssSectionAddr <= 0 || bssSectionSize <= 0) { + logger.fatal("Could not parse section addresses correctly"); + return false; + } + + // Create initial memory + byte[] initialDataSection = getCoreMemory(relDataSectionAddr + + offsetRelToAbs, dataSectionSize); + byte[] initialBssSection = getCoreMemory( + relBssSectionAddr + offsetRelToAbs, bssSectionSize); + initialMemory = new SectionMoteMemory(varAddresses); + initialMemory.setMemorySegment(relDataSectionAddr, initialDataSection); + initialMemory.setMemorySegment(relBssSectionAddr, initialBssSection); + + return false; + } + + /** + * Ticks the currently loaded mote. This should not be used directly, but + * rather via ContikiMote.tick(). + */ + public void tick() { + myCoreComm.tick(); + } + + /** + * Creates and returns a copy of this mote type's initial memory (just after + * the init function has been run). When a new mote is created it should get + * it's memory from here. + * + * @return Initial memory of a mote type + */ + public SectionMoteMemory createInitialMemory() { + return initialMemory.clone(); + } + + /** + * Copy given memory to the Contiki system. This should not be used directly, + * but instead via ContikiMote.setMemory(). + * + * @param mem + * New memory + */ + public void setCoreMemory(SectionMoteMemory mem) { + for (int i = 0; i < mem.getNumberOfSections(); i++) { + setCoreMemory(mem.getStartAddrOfSection(i) + offsetRelToAbs, mem + .getSizeOfSection(i), mem.getDataOfSection(i)); + } + } + + /** + * Copy core memory to given memory. This should not be used directly, but + * instead via ContikiMote.getMemory(). + * + * @param mem + * Memory to set + */ + public void getCoreMemory(SectionMoteMemory mem) { + for (int i = 0; i < mem.getNumberOfSections(); i++) { + int startAddr = mem.getStartAddrOfSection(i); + int size = mem.getSizeOfSection(i); + mem.setMemorySegment(startAddr, getCoreMemory(startAddr + offsetRelToAbs, + size)); + } + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + logger.warn("Contiki mote type is read-only"); + } + + /** + * @return Contiki mote type's library class name + */ + public String getLibraryClassName() { + return libraryClassName; + } + + /** + * Get relative address of variable with given name. + * + * @param varName + * Name of variable + * @return Relative memory address of variable or -1 if not found + */ + protected int getRelVarAddress(String varName) { + int varAddr; + String varAddrString; + if ((varAddrString = varAddresses.getProperty(varName)) != null) { + varAddr = Integer.parseInt(varAddrString); + return varAddr; + } + + String regExp = varAddressRegExpPrefix + varName + varAddressRegExpSuffix; + String retString = getFirstMatchGroup(mapContents, regExp, 1); + + if (retString != null) { + varAddresses.setProperty(varName, Integer.toString(Integer.parseInt( + retString.trim(), 16))); + return Integer.parseInt(retString.trim(), 16); + } else + return -1; + } + + private int getReferenceAbsAddr() { + return myCoreComm.getReferenceAbsAddr(); + } + + private byte[] getCoreMemory(int start, int length) { + return myCoreComm.getMemory(start, length); + } + + private void setCoreMemory(int start, int length, byte[] mem) { + myCoreComm.setMemory(start, length, mem); + } + + private static String getFirstMatchGroup(Vector lines, String regexp, + int groupNr) { + Pattern pattern = Pattern.compile(regexp); + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + return matcher.group(groupNr); + } + } + return null; + } + + /** + * Returns all variable names in both data and BSS section by parsing the map + * file. These values should not be trusted completely as the parsing may + * fail. + * + * @return Variable names found in the data and bss section + */ + public Vector getAllVariableNames() { + + Vector varNames = getAllVariableNames(mapContents, + loadRelDataSectionAddr(mapContents), + loadRelDataSectionAddr(mapContents) + loadDataSectionSize(mapContents)); + + varNames.addAll(getAllVariableNames(mapContents, + loadRelBssSectionAddr(mapContents), loadRelBssSectionAddr(mapContents) + + loadBssSectionSize(mapContents))); + + return varNames; + } + + private Vector getAllVariableNames(Vector lines, + int startAddress, int endAddress) { + Vector varNames = new Vector(); + + Pattern pattern = Pattern.compile(varNameRegExp); + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + if (Integer.decode(matcher.group(1)).intValue() >= startAddress + && Integer.decode(matcher.group(1)).intValue() <= endAddress) { + varNames.add(matcher.group(2)); + } + } + } + return varNames; + } + + protected int getVariableSize(Vector lines, String varName) { + Pattern pattern = Pattern.compile(varSizeRegExpPrefix + varName + + varSizeRegExpSuffix); + for (int i = 0; i < lines.size(); i++) { + Matcher matcher = pattern.matcher(lines.elementAt(i)); + if (matcher.find()) { + return Integer.decode(matcher.group(1)); + } + } + return -1; + } + + private static int loadRelDataSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else + return 0; + } + + private static int loadDataSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, dataSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else + return 0; + } + + private static int loadRelBssSectionAddr(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionAddrRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else + return 0; + } + + private static int loadBssSectionSize(Vector mapFile) { + String retString = getFirstMatchGroup(mapFile, bssSectionSizeRegExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else + return 0; + } + + private static int getRelVarAddr(Vector mapContents, String varName) { + String regExp = varAddressRegExpPrefix + varName + varAddressRegExpSuffix; + String retString = getFirstMatchGroup(mapContents, regExp, 1); + + if (retString != null) + return Integer.parseInt(retString.trim(), 16); + else + return 0; + } + + private static Vector loadMapFile(File mapFile) { + Vector mapContents = new Vector(); + + try { + BufferedReader in = new BufferedReader(new InputStreamReader( + new FileInputStream(mapFile))); + + while (in.ready()) { + mapContents.add(in.readLine()); + } + } catch (FileNotFoundException e) { + logger.fatal("File not found: " + e); + return null; + } catch (IOException e) { + logger.fatal("IO error: " + e); + return null; + } + + return mapContents; + } + + /** + * Returns simulation holding this mote type + * + * @return Simulation + */ + public Simulation getSimulation() { + return mySimulation; + } + + /** + * Sets simulation holding this mote type + * + * @param simulation + * Simulation holding this mote type + */ + public void setSimulation(Simulation simulation) { + mySimulation = simulation; + } + + public String getDescription() { + return description; + } + + public void setDescription(String newDescription) { + description = newDescription; + } + + /** + * Returns path to contiki base dir + * + * @return String containing path + */ + public String getContikiBaseDir() { + return contikiBaseDir; + } + + /** + * Sets contiki base dir to path. + * + * @param path + * Contiki base dir + */ + public void setContikiBaseDir(String path) { + contikiBaseDir = path; + } + + /** + * Returns path to contiki core dir + * + * @return String containing path + */ + public String getContikiCoreDir() { + return contikiCoreDir; + } + + /** + * Sets contiki core dir to path. + * + * @param path + * Contiki core dir + */ + public void setContikiCoreDir(String path) { + contikiCoreDir = path; + } + + /** + * Returns user platform directories + * + * @return User platform directories + */ + public Vector getUserPlatformDirs() { + return userPlatformDirs; + } + + /** + * Sets user platform directories. + * + * @param dirs + * New user platform directories + */ + public void setUserPlatformDirs(Vector dirs) { + userPlatformDirs = dirs; + } + + public PlatformConfig getConfig() { + return myConfig; + } + + /** + * Sets mote type platform configuration. This may differ from the general + * simulator platform configuration. + * + * @param moteTypeConfig + * Platform configuration + */ + public void setConfig(PlatformConfig moteTypeConfig) { + myConfig = moteTypeConfig; + } + + /** + * Returns all processes of this mote type + * + * @return All processes + */ + public Vector getProcesses() { + return processes; + } + + /** + * Set startup processes + * + * @param processes + * New startup processes + */ + public void setProcesses(Vector processes) { + this.processes = processes; + } + + /** + * Returns all sensors of this mote type + * + * @return All sensors + */ + public Vector getSensors() { + return sensors; + } + + /** + * Set sensors + * + * @param sensors + * New sensors + */ + public void setSensors(Vector sensors) { + this.sensors = sensors; + } + + /** + * Returns all core interfaces of this mote type + * + * @return All core interfaces + */ + public Vector getCoreInterfaces() { + return coreInterfaces; + } + + /** + * Set core interfaces + * + * @param coreInterfaces + * New core interfaces + */ + public void setCoreInterfaces(Vector coreInterfaces) { + this.coreInterfaces = coreInterfaces; + } + + /** + * Returns all mote interfaces of this mote type + * + * @return All mote interfaces + */ + public Vector> getMoteInterfaces() { + return moteInterfaces; + } + + /** + * Set mote interfaces of this mote type + * + * @param moteInterfaces + * New mote interfaces + */ + public void setMoteInterfaces( + Vector> moteInterfaces) { + this.moteInterfaces = moteInterfaces; + } + + /** + * Create a checksum of file. Used for checking if needed files are unchanged + * when loading a saved simulation. + * + * @param file + * File containg data to checksum + * @return Checksum + */ + protected byte[] createChecksum(File file) { + int bytesRead = 1; + byte[] readBytes = new byte[128]; + MessageDigest messageDigest; + + try { + InputStream fileInputStream = new FileInputStream(file); + messageDigest = MessageDigest.getInstance("MD5"); + + while (bytesRead > 0) { + bytesRead = fileInputStream.read(readBytes); + if (bytesRead > 0) + messageDigest.update(readBytes, 0, bytesRead); + } + fileInputStream.close(); + } catch (NoSuchAlgorithmException e) { + return null; + } catch (IOException e) { + return null; + } + return messageDigest.digest(); + } + + /** + * Returns a panel with interesting data for this mote type. + * + * @return Mote type visualizer + */ + public JPanel getTypeVisualizer() { + JPanel panel = new JPanel(); + JLabel label = new JLabel(); + JPanel smallPane; + + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + // Identifier + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Identifier"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(identifier); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Description + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Description"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(description); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Contiki dir + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Contiki path"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(contikiBaseDir); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Library class name + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("JNI Class"); + smallPane.add(BorderLayout.WEST, label); + label = new JLabel(libraryClassName); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + + // Processes + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Processes"); + smallPane.add(BorderLayout.WEST, label); + panel.add(smallPane); + + for (String process : processes) { + smallPane = new JPanel(new BorderLayout()); + label = new JLabel(process); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + } + + // Sensors + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Sensors"); + smallPane.add(BorderLayout.WEST, label); + panel.add(smallPane); + + for (String sensor : sensors) { + smallPane = new JPanel(new BorderLayout()); + label = new JLabel(sensor); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + } + + // Core Interfaces + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Core interfaces"); + smallPane.add(BorderLayout.WEST, label); + panel.add(smallPane); + + for (String mInterface : coreInterfaces) { + smallPane = new JPanel(new BorderLayout()); + label = new JLabel(mInterface); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + } + + // Mote Interfaces + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Mote interfaces"); + smallPane.add(BorderLayout.WEST, label); + panel.add(smallPane); + + for (Class moteInterface : moteInterfaces) { + smallPane = new JPanel(new BorderLayout()); + label = new JLabel(moteInterface.getSimpleName()); + smallPane.add(BorderLayout.EAST, label); + panel.add(smallPane); + } + + panel.add(Box.createRigidArea(new Dimension(0, 5))); + return panel; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Identifier + element = new Element("identifier"); + element.setText(getIdentifier()); + config.add(element); + + // Description + element = new Element("description"); + element.setText(getDescription()); + config.add(element); + + // Contiki base directory + element = new Element("contikibasedir"); + element.setText(getContikiBaseDir()); + config.add(element); + + // Contiki core directory + element = new Element("contikicoredir"); + element.setText(getContikiCoreDir()); + config.add(element); + + // User platform directory + for (File userPlatform: userPlatformDirs) { + element = new Element("userplatformdir"); + element.setText(userPlatform.getPath()); + config.add(element); + } + + // Contiki processes + for (String process : getProcesses()) { + element = new Element("process"); + element.setText(process); + config.add(element); + } + + // Contiki sensors + for (String sensor : getSensors()) { + element = new Element("sensor"); + element.setText(sensor); + config.add(element); + } + + // Mote interfaces + for (Class moteInterface : getMoteInterfaces()) { + element = new Element("moteinterface"); + element.setText(moteInterface.getName()); + config.add(element); + } + + // Core interfaces + for (String coreInterface : getCoreInterfaces()) { + element = new Element("coreinterface"); + element.setText(coreInterface); + config.add(element); + } + + return config; + } + + public boolean setConfigXML(Simulation simulation, + Collection configXML) { + userPlatformDirs = new Vector(); + processes = new Vector(); + sensors = new Vector(); + coreInterfaces = new Vector(); + moteInterfaces = new Vector>(); + mySimulation = simulation; + + for (Element element : configXML) { + String name = element.getName(); + + if (name.equals("identifier")) { + identifier = element.getText(); + } else if (name.equals("description")) { + description = element.getText(); + } else if (name.equals("contikibasedir")) { + contikiBaseDir = element.getText(); + } else if (name.equals("contikicoredir")) { + contikiCoreDir = element.getText(); + } else if (name.equals("userplatformdir")) { + userPlatformDirs.add(new File(element.getText())); + } else if (name.equals("process")) { + processes.add(element.getText()); + } else if (name.equals("sensor")) { + sensors.add(element.getText()); + } else if (name.equals("coreinterface")) { + coreInterfaces.add(element.getText()); + } else if (name.equals("moteinterface")) { + Class moteInterfaceClass = + GUI.currentGUI.tryLoadClass(this, MoteInterface.class, element.getText().trim()); + + if (moteInterfaceClass == null) { + logger.warn("Can't find mote interface class: " + element.getText()); + } else + moteInterfaces.add(moteInterfaceClass); + } else { + logger.fatal("Unrecognized entry in loaded configuration: " + name); + } + } + + boolean createdOK = configureAndInit(GUI.frame, simulation); + return createdOK; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteTypeDialog.java b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteTypeDialog.java new file mode 100644 index 000000000..15464caeb --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/ContikiMoteTypeDialog.java @@ -0,0 +1,2106 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiMoteTypeDialog.java,v 1.1 2006/08/21 12:13:10 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.regex.*; +import javax.swing.*; +import javax.swing.event.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.dialogs.MessageList; +import se.sics.cooja.dialogs.UserPlatformsDialog; + +/** + * A dialog for configuring Contiki mote types and compiling Contiki mote type + * libraries. Allows user to change mote type specific data such as + * descriptions, which processes should be started at mote initialization and + * which interfaces the type should support. + * + * The dialog takes a Contiki mote type as argument and pre-selects the values + * already set in that mote type before showing the dialog. Any changes made to + * the settings are written to the mote type if the compilation is successful + * and the user presses OK. + * + * This dialog uses external tools to scan for sources and compile libraries. + * + * @author Fredrik Osterlind + */ +public class ContikiMoteTypeDialog extends JDialog { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(ContikiMoteTypeDialog.class); + + private MoteTypeEventHandler myEventHandler = new MoteTypeEventHandler(); + private Thread compilationThread; + + /** + * Suggested mote type identifier prefix + */ + public static final String ID_PREFIX = "mtype"; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private ContikiMoteType myMoteType = null; + + private JTextField textID, textOutputFiles, textDescription, textContikiDir, + textCoreDir, textUserPlatforms; + private JButton createButton, testButton, rescanButton; + + private JPanel processPanel; // Holds process checkboxes + private JPanel sensorPanel; // Holds sensor checkboxes + private JPanel moteInterfacePanel; // Holds mote interface checkboxes + private JPanel coreInterfacePanel; // Holds core interface checkboxes + + private JPanel entireSensorPane; // Holds entire sensor pane (can be hidden) + private JPanel entireCoreInterfacePane; // Holds entire core interface pane + // (can be hidden) + + private boolean settingsOK = false; // Do all settings seem correct? + private boolean compilationSucceded = false; // Did compilation succeed? + private boolean libraryCreatedOK = false; // Was a library created? + + private PlatformConfig newMoteTypeConfig = null; // Mote type platform config + private Vector moteTypeUserPlatforms = new Vector(); // Mote type + // user + // platforms + + private Vector allOtherTypes = null; // Used to check for + // conflicting parameters + + private Frame myParentFrame; + private ContikiMoteTypeDialog myDialog; + + /** + * Shows a dialog for configuring a Contiki mote type and compiling the shared + * library it uses. + * + * @param parentFrame + * Parent frame for dialog + * @param simulation + * Simulation holding (or that will hold) mote type + * @param moteTypeToConfigure + * Mote type to configure + * @return True if compilation succeded and library is ready to be loaded + */ + public static boolean showDialog(Frame parentFrame, Simulation simulation, + ContikiMoteType moteTypeToConfigure) { + + final ContikiMoteTypeDialog myDialog = new ContikiMoteTypeDialog( + parentFrame); + + myDialog.myMoteType = moteTypeToConfigure; + myDialog.allOtherTypes = simulation.getMoteTypes(); + + // Set identifier of mote type + if (moteTypeToConfigure.getIdentifier() != null) { + // Identifier already preset, assuming recompilation of mote type library + // Use preset identifier (read-only) + myDialog.textID.setText(moteTypeToConfigure.getIdentifier()); + myDialog.textID.setEditable(false); + myDialog.textID.setEnabled(false); + + // Change title to indicate this is a recompilation + myDialog.setTitle("Recompile Mote Type"); + } else { + // Suggest new identifier + int counter = 0; + String testIdentifier = ""; + boolean identifierOK = false; + while (!identifierOK) { + counter++; + testIdentifier = ID_PREFIX + counter; + identifierOK = true; + + // Check if identifier is already used by some other type + for (MoteType existingMoteType : myDialog.allOtherTypes) { + if (existingMoteType != myDialog.myMoteType + && existingMoteType.getIdentifier().equals(testIdentifier)) { + identifierOK = false; + break; + } + } + + // Check if library file with given identifier has already been loaded + if (identifierOK + && CoreComm.hasLibraryFileBeenLoaded(new File( + ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + testIdentifier + + ContikiMoteType.librarySuffix))) { + identifierOK = false; + } + } + + myDialog.textID.setText(testIdentifier); + + } + + // Set preset description of mote type + if (moteTypeToConfigure.getDescription() != null) { + myDialog.textDescription.setText(moteTypeToConfigure.getDescription()); + } + + // Set preset Contiki base directory of mote type + if (moteTypeToConfigure.getContikiBaseDir() != null) { + myDialog.textContikiDir.setText(moteTypeToConfigure.getContikiBaseDir()); + } + + // Set preset Contiki core directory of mote type + if (moteTypeToConfigure.getContikiCoreDir() != null) { + myDialog.textCoreDir.setText(moteTypeToConfigure.getContikiCoreDir()); + } + + // Set preset user platform directories of mote type + if (moteTypeToConfigure.getUserPlatformDirs() != null) { + myDialog.moteTypeUserPlatforms = moteTypeToConfigure + .getUserPlatformDirs(); + String userPlatformText = null; + for (File userPlatform : myDialog.moteTypeUserPlatforms) { + if (userPlatformText == null) + userPlatformText = "'" + userPlatform.getPath() + "'"; + else + userPlatformText += ", '" + userPlatform.getPath() + "'"; + } + myDialog.textUserPlatforms.setText(userPlatformText); + } + + // Scan directories for processes, sensors and core interfaces + // TODO Really do this without starting a separate thread? + myDialog.updateVisualFields(); + myDialog.rescanDirectories(); + + // Select preset processes of mote type + if (moteTypeToConfigure.getProcesses() != null) { + for (String presetProcess : moteTypeToConfigure.getProcesses()) { + // Try to find process in current list + boolean foundAndSelectedProcess = false; + for (Component processCheckBox : myDialog.processPanel.getComponents()) { + if (presetProcess.equals(((JCheckBox) processCheckBox).getText())) { + ((JCheckBox) processCheckBox).setSelected(true); + foundAndSelectedProcess = true; + break; + } + } + + // Warn if not found + if (!foundAndSelectedProcess) { + logger.warn("Process was not found in current environment: " + + presetProcess); + } + } + } + + // Select preset sensors + if (moteTypeToConfigure.getSensors() != null) { + // Deselect all sensors already automatically selected + for (Component coreInterfaceCheckBox : myDialog.sensorPanel + .getComponents()) { + ((JCheckBox) coreInterfaceCheckBox).setSelected(false); + } + + for (String presetSensor : moteTypeToConfigure.getSensors()) { + // Try to find sensor in current list + boolean foundAndSelectedSensor = false; + for (Component sensorCheckBox : myDialog.sensorPanel.getComponents()) { + if (presetSensor.equals(((JCheckBox) sensorCheckBox).getText())) { + ((JCheckBox) sensorCheckBox).setSelected(true); + foundAndSelectedSensor = true; + break; + } + } + + // Warn if not found + if (!foundAndSelectedSensor) { + logger.warn("Sensor was not found in current environment: " + + presetSensor); + } + } + } + + // Select preset core interfaces + if (moteTypeToConfigure.getCoreInterfaces() != null) { + // Deselect all core interfaces already automatically selected + for (Component coreInterfaceCheckBox : myDialog.coreInterfacePanel + .getComponents()) { + ((JCheckBox) coreInterfaceCheckBox).setSelected(false); + } + + for (String presetCoreInterface : moteTypeToConfigure.getCoreInterfaces()) { + // Try to find core interface in current list + boolean foundAndSelectedCoreInterface = false; + for (Component coreInterfaceCheckBox : myDialog.coreInterfacePanel + .getComponents()) { + if (presetCoreInterface.equals(((JCheckBox) coreInterfaceCheckBox) + .getText())) { + ((JCheckBox) coreInterfaceCheckBox).setSelected(true); + foundAndSelectedCoreInterface = true; + break; + } + } + + // Warn if not found + if (!foundAndSelectedCoreInterface) { + logger.warn("Core interface was not found in current environment: " + + presetCoreInterface); + } + } + } + + // Select preset mote interfaces + if (moteTypeToConfigure.getMoteInterfaces() != null) { + // Deselect all mote interfaces already automatically selected + for (Component moteInterfaceCheckBox : myDialog.moteInterfacePanel + .getComponents()) { + ((JCheckBox) moteInterfaceCheckBox).setSelected(false); + } + + for (Class presetMoteInterface : moteTypeToConfigure.getMoteInterfaces()) { + // Try to find mote interface in current list + boolean foundAndSelectedMoteInterface = false; + for (Component moteInterfaceCheckBox : myDialog.moteInterfacePanel + .getComponents()) { + Class moteInterfaceClass = (Class) ((JCheckBox) moteInterfaceCheckBox) + .getClientProperty("class"); + + if (presetMoteInterface == moteInterfaceClass) { + ((JCheckBox) moteInterfaceCheckBox).setSelected(true); + foundAndSelectedMoteInterface = true; + break; + } + } + + // Warn if not found + if (!foundAndSelectedMoteInterface) { + logger.warn("Mote interface was not found in current environment: " + + presetMoteInterface); + } + } + } + + // Set position and focus of dialog + myDialog.setLocationRelativeTo(parentFrame); + myDialog.textDescription.requestFocus(); + myDialog.textDescription.select(0, myDialog.textDescription.getText() + .length()); + + myDialog.setVisible(true); + + if (myDialog.myMoteType != null) { + // Library was compiled and loaded + return true; + } + return false; + } + + private ContikiMoteTypeDialog(Frame frame) { + super(frame, "Add Mote Type", true); + + myDialog = this; + myParentFrame = frame; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + JTextField textField; + JButton button; + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + button = new JButton("Test current settings"); + button.setActionCommand("testsettings"); + button.addActionListener(myEventHandler); + testButton = button; + this.getRootPane().setDefaultButton(button); + buttonPane.add(button); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Clean intermediate files"); + button.setActionCommand("clean"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + button = new JButton("Cancel"); + button.setActionCommand("cancel"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + button = new JButton("Create"); + button.setEnabled(libraryCreatedOK); + button.setActionCommand("create"); + button.addActionListener(myEventHandler); + createButton = button; + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + // MAIN PART + + // Identifier + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Identifier"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + + textField.setText(""); + + textField.getDocument().addDocumentListener(myEventHandler); + textID = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Output filenames + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Output files"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.mapSuffix + + ", " + ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.librarySuffix + + ", " + ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.dependSuffix); + textField.setEnabled(false); + textOutputFiles = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Description + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Description"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setBackground(Color.GREEN); + textField.setText("[enter description here]"); + textField.getDocument().addDocumentListener(myEventHandler); + textDescription = textField; + label.setLabelFor(textField); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Contiki dir + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Contiki 2.x OS path"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(GUI.getExternalToolsSetting("PATH_CONTIKI")); + textField.getDocument().addDocumentListener(myEventHandler); + textContikiDir = textField; + label.setLabelFor(textField); + + button = new JButton("Browse"); + button.setActionCommand("browsecontiki"); + button.addActionListener(myEventHandler); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + smallPane.add(button); + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // COOJA core platform dir + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Core platform path"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(textContikiDir.getText() + + GUI.getExternalToolsSetting("PATH_COOJA_CORE_RELATIVE")); + textField.setEditable(false); + textCoreDir = textField; + label.setLabelFor(textField); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // COOJA user platform dir + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Mote type user platforms"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText(""); + textField.setEditable(false); + textUserPlatforms = textField; + label.setLabelFor(textField); + + button = new JButton("Manage"); + button.setActionCommand("manageuserplatforms"); + button.addActionListener(myEventHandler); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(textField); + smallPane.add(button); + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Separator + mainPane.add(new JSeparator()); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Rescan button + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Scan after entering above information"); + + rescanButton = new JButton("Scan now"); + rescanButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + rescanDirectories(); + } + }); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(rescanButton); + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Separator + mainPane.add(new JSeparator()); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Processes + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Processes"); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + label.setAlignmentY(Component.TOP_ALIGNMENT); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + JPanel processHolder = new JPanel(new BorderLayout()); + processHolder.setAlignmentX(Component.LEFT_ALIGNMENT); + processHolder.setAlignmentY(Component.TOP_ALIGNMENT); + + button = new JButton("Add process name"); + button.setActionCommand("addprocess"); + button.addActionListener(myEventHandler); + processHolder.add(BorderLayout.SOUTH, button); + + processPanel = new JPanel(); + + processPanel.setLayout(new BoxLayout(processPanel, BoxLayout.Y_AXIS)); + + JScrollPane tempPane = new JScrollPane(processPanel); + tempPane.setPreferredSize(new Dimension(300, 200)); + processHolder.add(BorderLayout.WEST, tempPane); + + label.setLabelFor(processPanel); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(processHolder); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Mote interfaces + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Mote Interfaces"); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + label.setAlignmentY(Component.TOP_ALIGNMENT); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + JPanel moteInterfaceHolder = new JPanel(new BorderLayout()); + moteInterfaceHolder.setAlignmentX(Component.LEFT_ALIGNMENT); + moteInterfaceHolder.setAlignmentY(Component.TOP_ALIGNMENT); + + moteInterfacePanel = new JPanel(); + + moteInterfacePanel.setLayout(new BoxLayout(moteInterfacePanel, + BoxLayout.Y_AXIS)); + + tempPane = new JScrollPane(moteInterfacePanel); + tempPane.setPreferredSize(new Dimension(300, 200)); + moteInterfaceHolder.add(BorderLayout.WEST, tempPane); + + label.setLabelFor(moteInterfacePanel); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(moteInterfaceHolder); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Separator with show advanced checkbox + JCheckBox showAdvancedCheckBox = new JCheckBox("Show advanced settings"); + showAdvancedCheckBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (((JCheckBox) e.getSource()).isSelected()) { + if (entireCoreInterfacePane != null) + entireCoreInterfacePane.setVisible(true); + if (entireSensorPane != null) + entireSensorPane.setVisible(true); + } else { + if (entireCoreInterfacePane != null) + entireCoreInterfacePane.setVisible(false); + if (entireSensorPane != null) + entireSensorPane.setVisible(false); + } + } + }); + mainPane.add(new JSeparator()); + mainPane.add(showAdvancedCheckBox); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Core sensors + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Sensors"); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + label.setAlignmentY(Component.TOP_ALIGNMENT); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + JPanel sensorHolder = new JPanel(new BorderLayout()); + sensorHolder.setAlignmentX(Component.LEFT_ALIGNMENT); + sensorHolder.setAlignmentY(Component.TOP_ALIGNMENT); + + // button = new JButton(GUI.lang.getString("motetype_scansens")); + // button.setActionCommand("scansensors"); + // button.addActionListener(myEventHandler); + // sensorHolder.add(BorderLayout.NORTH, button); + + button = new JButton("Add sensor name"); + button.setActionCommand("addsensor"); + button.addActionListener(myEventHandler); + sensorHolder.add(BorderLayout.SOUTH, button); + + sensorPanel = new JPanel(); + + sensorPanel.setLayout(new BoxLayout(sensorPanel, BoxLayout.Y_AXIS)); + + JScrollPane tempPane2 = new JScrollPane(sensorPanel); + tempPane2.setPreferredSize(new Dimension(300, 200)); + sensorHolder.add(BorderLayout.WEST, tempPane2); + + label.setLabelFor(sensorPanel); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(sensorHolder); + + mainPane.add(smallPane); + entireSensorPane = smallPane; + entireSensorPane.setVisible(false); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Core interfaces + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Core Interfaces"); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + label.setAlignmentY(Component.TOP_ALIGNMENT); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + JPanel interfaceHolder = new JPanel(new BorderLayout()); + interfaceHolder.setAlignmentX(Component.LEFT_ALIGNMENT); + interfaceHolder.setAlignmentY(Component.TOP_ALIGNMENT); + + button = new JButton("Add interface name"); + button.setActionCommand("addinterface"); + button.addActionListener(myEventHandler); + interfaceHolder.add(BorderLayout.SOUTH, button); + + coreInterfacePanel = new JPanel(); + + coreInterfacePanel.setLayout(new BoxLayout(coreInterfacePanel, + BoxLayout.Y_AXIS)); + + JScrollPane tempPane3 = new JScrollPane(coreInterfacePanel); + tempPane3.setPreferredSize(new Dimension(300, 200)); + interfaceHolder.add(BorderLayout.WEST, tempPane3); + + label.setLabelFor(coreInterfacePanel); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + smallPane.add(interfaceHolder); + + mainPane.add(smallPane); + entireCoreInterfacePane = smallPane; + entireCoreInterfacePane.setVisible(false); + + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Add everything! + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + Container contentPane = getContentPane(); + JScrollPane mainScrollPane = new JScrollPane(mainPane); + mainScrollPane.setPreferredSize(new Dimension( + mainPane.getPreferredSize().width + 50, 500)); + contentPane.add(mainScrollPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + pack(); + } + + /** + * Checks which core interfaces are needed by the currently selected mote + * interfaces, and selects only them. + */ + private void recheckInterfaceDependencies() { + + // Unselect all core interfaces + for (Component checkBox : coreInterfacePanel.getComponents()) { + ((JCheckBox) checkBox).setSelected(false); + } + + // Loop through all mote interfaces + for (Component checkBox : moteInterfacePanel.getComponents()) { + JCheckBox moteIntfCheckBox = (JCheckBox) checkBox; + + // If the mote interface is selected, select needed core interfaces + if (moteIntfCheckBox.isSelected()) { + String[] neededCoreInterfaces = null; + + // Get needed core interfaces (if any) + try { + Class moteInterfaceClass = (Class) moteIntfCheckBox + .getClientProperty("class"); + if (ContikiMoteInterface.class.isAssignableFrom(moteInterfaceClass)) { + Method m = moteInterfaceClass.getDeclaredMethod( + "getCoreInterfaceDependencies", (Class[]) null); + neededCoreInterfaces = (String[]) m.invoke(null, (Object[]) null); + } + } catch (NoSuchMethodException e) { + logger.warn("Can't read core interface dependencies of " + + moteIntfCheckBox.getText() + ", assuming no core dependencies"); + } catch (InvocationTargetException e) { + logger.warn("Can't read core interface dependencies of " + + moteIntfCheckBox.getText() + ": " + e); + } catch (IllegalAccessException e) { + logger.warn("Can't read core interface dependencies of " + + moteIntfCheckBox.getText() + ": " + e); + } + + // If needed core interfaces found, select them + if (neededCoreInterfaces != null) { + // Loop through all needed core interfaces + for (String neededCoreInterface : neededCoreInterfaces) { + int coreInterfacePosition = -1; + + // Check that the core interface actually exists + for (int j = 0; j < coreInterfacePanel.getComponentCount(); j++) { + JCheckBox coreCheckBox = (JCheckBox) coreInterfacePanel + .getComponent(j); + + if (coreCheckBox.getText().equals(neededCoreInterface)) { + coreInterfacePosition = j; + coreCheckBox.setSelected(true); + break; + } + } + + // Was the core interface found? + if (coreInterfacePosition < 0) { + logger.warn("Warning! " + moteIntfCheckBox.getText() + + " needs non-existing core interface " + neededCoreInterface + + " (rescan?)"); + } + } + } + + } + } + } + + /** + * Tries to compile library using current settings. + */ + public void doTestSettings() { + libraryCreatedOK = false; + + JPanel progressPanel = new JPanel(new BorderLayout()); + final JDialog progressDialog = new JDialog(myDialog, null); + JProgressBar progressBar; + JButton button; + final MessageList taskOutput; + progressDialog.setLocationRelativeTo(myDialog); + + progressBar = new JProgressBar(0, 100); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setIndeterminate(true); + + taskOutput = new MessageList(); + + button = new JButton("Close/Abort"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (compilationThread != null && compilationThread.isAlive()) { + compilationThread.interrupt(); + } + progressDialog.dispose(); + } + }); + + progressPanel.add(BorderLayout.CENTER, new JScrollPane(taskOutput)); + progressPanel.add(BorderLayout.NORTH, progressBar); + progressPanel.add(BorderLayout.SOUTH, button); + progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + progressPanel.setVisible(true); + + progressDialog.getContentPane().add(progressPanel); + progressDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + progressDialog.pack(); + + progressDialog.setVisible(true); + + // Create temp output directory if not already exists + if (!ContikiMoteType.tempOutputDirectory.exists()) + ContikiMoteType.tempOutputDirectory.mkdir(); + + // Generate main contiki source file + String errorMessage = generateSourceFile(); + if (errorMessage != null) { + libraryCreatedOK = false; + progressBar.setBackground(Color.ORANGE); + progressBar.setString("Maximum number of mote types already exist"); + progressBar.setIndeterminate(false); + progressBar.setValue(0); + createButton.setEnabled(libraryCreatedOK); + return; + } + + // Test compile shared library + progressBar.setString("..compiling.."); + final File contikiDir = new File(textContikiDir.getText()); + final String identifier = textID.getText(); + File libFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.librarySuffix); + File mapFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.mapSuffix); + File depFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.dependSuffix); + + if (libFile.exists()) { + libFile.delete(); + } + + if (depFile.exists()) { + depFile.delete(); + } + + if (mapFile.exists()) { + mapFile.delete(); + } + + compilationThread = new Thread(new Runnable() { + public void run() { + // Merge user platforms + Vector allUserPlatforms = (Vector) GUI.currentGUI.getUserPlatforms().clone(); + allUserPlatforms.addAll(moteTypeUserPlatforms); + compilationSucceded = ContikiMoteTypeDialog.compileLibrary(contikiDir, + allUserPlatforms, identifier, taskOutput, newMoteTypeConfig); + } + }, "compilation thread"); + compilationThread.start(); + + while (compilationThread.isAlive()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // NOP + } + } + + if (!compilationSucceded) { + if (libFile.exists()) { + libFile.delete(); + } + if (depFile.exists()) { + depFile.delete(); + } + if (mapFile.exists()) { + mapFile.delete(); + } + libraryCreatedOK = false; + } else { + libraryCreatedOK = true; + if (!libFile.exists() || !depFile.exists() || !mapFile.exists()) + libraryCreatedOK = false; + } + + if (libraryCreatedOK) { + progressBar.setBackground(Color.GREEN); + progressBar.setString("compilation succeded"); + } else { + progressBar.setBackground(Color.ORANGE); + progressBar.setString("compilation failed"); + } + progressBar.setIndeterminate(false); + progressBar.setValue(0); + createButton.setEnabled(libraryCreatedOK); + } + + /** + * @return Error message or null if source file generation ok + */ + private String generateSourceFile() { + + // SENSORS + String sensorString = ""; + String externSensorDefs = ""; + for (Component checkBox : sensorPanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + if (!sensorString.equals("")) + sensorString = sensorString.concat(", "); + sensorString = sensorString.concat("&" + + ((JCheckBox) checkBox).getText()); + externSensorDefs = externSensorDefs + .concat("extern const struct sensors_sensor " + + ((JCheckBox) checkBox).getText() + ";\n"); + } + } + + if (!sensorString.equals("")) + sensorString = "SENSORS(" + sensorString + ");"; + else + sensorString = "SENSORS(NULL);"; + + // CORE INTERFACES + String externInterfaceDefs = ""; + String interfaceString = ""; + for (Component checkBox : coreInterfacePanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + if (!interfaceString.equals("")) + interfaceString = interfaceString.concat(", "); + interfaceString = interfaceString.concat("&" + + ((JCheckBox) checkBox).getText()); + externInterfaceDefs = externInterfaceDefs.concat("SIM_INTERFACE_NAME(" + + ((JCheckBox) checkBox).getText() + ");\n"); + } + } + + if (!interfaceString.equals("")) + interfaceString = "SIM_INTERFACES(" + interfaceString + ");"; + else + interfaceString = "SIM_INTERFACES(NULL);"; + + // PROCESSES + String userProcessString = ""; + String externProcessDefs = ""; + for (Component checkBox : processPanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + if (!userProcessString.equals("")) + userProcessString = userProcessString.concat(", "); + userProcessString = userProcessString.concat("&" + + ((JCheckBox) checkBox).getText()); + externProcessDefs = externProcessDefs.concat("PROCESS_NAME(" + + ((JCheckBox) checkBox).getText() + ");\n"); + } + } + + String coreProcessString = ""; + String processArray[] = GUI.getExternalToolsSetting( + "CONTIKI_STANDARD_PROCESSES").split(";"); + for (String processString : processArray) { + if (!coreProcessString.equals("")) + coreProcessString = coreProcessString.concat(", "); + coreProcessString = coreProcessString + "&" + processString; + } + + if (userProcessString.equals("")) { + logger + .warn("No application processes specified! Sure you don't want any?"); + } + + String processString; + if (!coreProcessString.equals("")) + processString = "PROCINIT(" + coreProcessString + ");"; + else + processString = "PROCINIT(NULL);"; + + if (!userProcessString.equals("")) + processString = processString.concat("\nAUTOSTART_PROCESSES(" + + userProcessString + ");"); + else + processString = processString.concat("\nAUTOSTART_PROCESSES(NULL);"); + + // JNI CLASS NAME + String libString = CoreComm.getAvailableClassName(); + if (libString == null) { + logger.fatal("No more libraries can be loaded!"); + return "Maximum number of mote types already exist"; + } + + // Create new source file and replace special fields + try { + Reader reader; + String mainTemplate = GUI + .getExternalToolsSetting("CONTIKI_MAIN_TEMPLATE_FILENAME"); + if ((new File(mainTemplate)).exists()) { + reader = new FileReader(mainTemplate); + } else { + InputStream input = ContikiMoteTypeDialog.class + .getResourceAsStream(File.separatorChar + mainTemplate); + if (input == null) { + throw new FileNotFoundException(mainTemplate + " not found"); + } + reader = new InputStreamReader(input); + } + + BufferedReader sourceFile = new BufferedReader(reader); + BufferedWriter destFile = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ".c"))); + String line; + while ((line = sourceFile.readLine()) != null) { + line = line + .replaceFirst("\\[PROCESS_DEFINITIONS\\]", externProcessDefs); + line = line.replaceFirst("\\[PROCESS_ARRAY\\]", processString); + + line = line.replaceFirst("\\[SENSOR_DEFINITIONS\\]", externSensorDefs); + line = line.replaceFirst("\\[SENSOR_ARRAY\\]", sensorString); + + line = line.replaceFirst("\\[INTERFACE_DEFINITIONS\\]", + externInterfaceDefs); + line = line.replaceFirst("\\[INTERFACE_ARRAY\\]", interfaceString); + + line = line.replaceFirst("\\[CLASS_NAME\\]", libString); + destFile.write(line + "\n"); + } + + destFile.close(); + sourceFile.close(); + } catch (Exception e) { + logger.debug("Exception " + e); + return "Exception " + e; + } + + return null; + } + + /** + * Compiles a mote type shared library using an external makefile. + * + * @param contikiDir + * Contiki OS main directory + * @param userPlatformDirs + * COOJA user platforms (may be null) + * @param identifier + * Filename identifier + * @param appender + * Optional component to append process output to + * @return False if an error was discovered, true otherwise + */ + public static boolean compileLibrary(File contikiDir, Vector userPlatformDirs, + String identifier, final MessageList appender, + PlatformConfig platformConfig) { + File libFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.librarySuffix); + File mapFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.mapSuffix); + File depFile = new File(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + identifier + ContikiMoteType.dependSuffix); + + // Recheck that contiki path exists + if (!contikiDir.exists()) { + appender.addMessage("Bad Contiki OS path", MessageList.ERROR); + logger.fatal("Contiki path does not exist"); + return false; + } + if (!contikiDir.isDirectory()) { + appender.addMessage("Bad Contiki OS path", MessageList.ERROR); + logger.fatal("Contiki path is not a directory"); + return false; + } + + if (libFile.exists()) { + appender.addMessage("Bad output filenames", MessageList.ERROR); + logger.fatal("Could not overwrite already existing library"); + return false; + } + + if (CoreComm.hasLibraryFileBeenLoaded(libFile)) { + appender.addMessage("Bad output filenames", MessageList.ERROR); + logger + .fatal("A library has already been loaded with the same name before"); + return false; + } + + if (depFile.exists()) { + appender.addMessage("Bad output filenames", MessageList.ERROR); + logger.fatal("Could not overwrite already existing dependency file"); + return false; + } + + if (mapFile.exists()) { + appender.addMessage("Bad output filenames", MessageList.ERROR); + logger.fatal("Could not overwrite already existing map file"); + return false; + } + + try { + // Let Contiki 2.x regular make file compile + String[] cmd = new String[]{ + GUI.getExternalToolsSetting("PATH_MAKE"), + libFile.getPath().replace(File.separatorChar, '/'), + "-f", + contikiDir.getPath().replace(File.separatorChar, '/') + + "/Makefile.include"}; + + String projectDirs = System.getProperty("PROJECTDIRS", ""); + if (userPlatformDirs != null) { + for (File userPlatform : userPlatformDirs) { + projectDirs += " " + + userPlatform.getPath().replace(File.separatorChar, '/'); + } + } + + String[] projectSourceFiles = platformConfig.getStringArrayValue( + ContikiMoteType.class, "C_SOURCES"); + String sourceFiles = ""; + if (userPlatformDirs != null) { + for (String sourceFile : projectSourceFiles) { + sourceFiles += " " + sourceFile; + } + } + + String[] env = new String[]{ + "CONTIKI=" + contikiDir.getPath().replace(File.separatorChar, '/'), + "TARGET=cooja", "TYPEID=" + identifier, + "LD_ARGS_1=" + GUI.getExternalToolsSetting("LINKER_ARGS_1", ""), + "LD_ARGS_2=" + GUI.getExternalToolsSetting("LINKER_ARGS_2", ""), + "EXTRA_CC_ARGS=" + GUI.getExternalToolsSetting("COMPILER_ARGS", ""), + "CC=" + GUI.getExternalToolsSetting("PATH_C_COMPILER"), + "LD=" + GUI.getExternalToolsSetting("PATH_LINKER"), "COMPILE_MAIN=1", + "PROJECTDIRS=" + projectDirs, "PROJECT_SOURCEFILES=" + sourceFiles, + "PATH=" + System.getenv("PATH")}; + + Process p = Runtime.getRuntime().exec(cmd, env, null); + + final BufferedReader input = new BufferedReader(new InputStreamReader(p + .getInputStream())); + final BufferedReader err = new BufferedReader(new InputStreamReader(p + .getErrorStream())); + + Thread readInput = new Thread(new Runnable() { + public void run() { + String readLine; + try { + while ((readLine = input.readLine()) != null) { + if (appender != null && readLine != null) { + appender.addMessage(readLine); + } + } + } catch (IOException e) { + logger.warn("Error while reading from process"); + } + } + }, "read input stream thread"); + + Thread readError = new Thread(new Runnable() { + public void run() { + String readLine; + try { + while ((readLine = err.readLine()) != null) { + if (appender != null && readLine != null) { + appender.addMessage(readLine, MessageList.ERROR); + } + } + } catch (IOException e) { + logger.warn("Error while reading from process"); + } + } + }, "read input stream thread"); + + readInput.start(); + readError.start(); + + while (readInput.isAlive() || readError.isAlive()) { + Thread.sleep(100); + } + + input.close(); + err.close(); + + p.waitFor(); + if (p.exitValue() != 0) + return false; + } catch (Exception e) { + logger.fatal("Error while compiling library: " + e); + return false; + } + return true; + } + + /** + * Scans a directory for sourcefiles which defines a Contiki process. + * + * @param rootDirectory + * Top directory to search in + * @return Process definitions found under rootDirectory, {sourcefile, + * processname} + */ + public static Vector scanForProcesses(File rootDirectory) { + if (!rootDirectory.isDirectory()) { + logger.fatal("Not a directory: " + rootDirectory); + return null; + } + + if (!rootDirectory.exists()) { + logger.fatal("Does not exist: " + rootDirectory); + return null; + } + + Vector processes = new Vector(); + + // Scan in rootDirectory + try { + String line; + String cmdString = GUI.getExternalToolsSetting("CMD_GREP_PROCESSES") + + " '" + rootDirectory.getPath().replace(File.separatorChar, '/') + + "'/*.[ch]"; + Pattern pattern = Pattern.compile(GUI + .getExternalToolsSetting("REGEXP_PARSE_PROCESSES")); + + String[] cmd = new String[3]; + cmd[0] = GUI.getExternalToolsSetting("PATH_SHELL"); + cmd[1] = "-c"; + cmd[2] = cmdString; + + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader input = new BufferedReader(new InputStreamReader(p + .getInputStream())); + while ((line = input.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + processes.add(new String[]{matcher.group(1), matcher.group(2)}); + } + } + input.close(); + + BufferedReader err = new BufferedReader(new InputStreamReader(p + .getErrorStream())); + if (err.ready()) + logger.warn("Error occured during scan:"); + while ((line = err.readLine()) != null) { + logger.warn(line); + } + err.close(); + } catch (IOException err) { + logger.fatal("Error while scanning for processes: " + err); + err.printStackTrace(); + } catch (Exception err) { + logger.fatal("Error while scanning for processes: " + err); + err.printStackTrace(); + } + return processes; + } + + /** + * Scans a directory and all subdirectories for sourcefiles which defines a + * Contiki sensor. + * + * @param rootDirectory + * Top directory to search in + * @return Sensor definitions found under rootDirectory, {sourcefile, + * sensorname} + */ + public static Vector scanForSensors(File rootDirectory) { + if (!rootDirectory.isDirectory()) { + logger.fatal("Not a directory: " + rootDirectory); + return null; + } + + if (!rootDirectory.exists()) { + logger.fatal("Does not exist: " + rootDirectory); + return null; + } + + Vector sensors = new Vector(); + + // Scan in rootDirectory + try { + String line; + String cmdString = GUI.getExternalToolsSetting("CMD_GREP_SENSORS") + " '" + + rootDirectory.getPath().replace(File.separatorChar, '/') + "'"; + Pattern pattern = Pattern.compile(GUI + .getExternalToolsSetting("REGEXP_PARSE_SENSORS")); + + String[] cmd = new String[3]; + cmd[0] = GUI.getExternalToolsSetting("PATH_SHELL"); + cmd[1] = "-c"; + cmd[2] = cmdString; + + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader input = new BufferedReader(new InputStreamReader(p + .getInputStream())); + while ((line = input.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + sensors.add(new String[]{matcher.group(1), matcher.group(2)}); + } + } + input.close(); + + BufferedReader err = new BufferedReader(new InputStreamReader(p + .getErrorStream())); + if (err.ready()) + logger.warn("Error occured during scan:"); + while ((line = err.readLine()) != null) { + logger.warn(line); + } + err.close(); + } catch (IOException err) { + logger.fatal("Error while scanning for sensors: " + err); + err.printStackTrace(); + } catch (Exception err) { + logger.fatal("Error while scanning for sensors: " + err); + err.printStackTrace(); + } + return sensors; + } + + /** + * Scans a directory and all subdirectories for sourcefiles which defines a + * COOJA core interface. + * + * @param rootDirectory + * Top directory to search in + * @return Core interface definitions found under rootDirectory, {sourcefile, + * interfacename} + */ + public static Vector scanForInterfaces(File rootDirectory) { + if (!rootDirectory.isDirectory()) { + logger.fatal("Not a directory: " + rootDirectory); + return null; + } + + if (!rootDirectory.exists()) { + logger.fatal("Does not exist: " + rootDirectory); + return null; + } + + Vector interfaces = new Vector(); + + // Scan in rootDirectory + try { + String line; + String cmdString = GUI.getExternalToolsSetting("CMD_GREP_INTERFACES") + + " '" + rootDirectory.getPath().replace(File.separatorChar, '/') + + "'"; + Pattern pattern = Pattern.compile(GUI + .getExternalToolsSetting("REGEXP_PARSE_INTERFACES")); + + String[] cmd = new String[3]; + cmd[0] = GUI.getExternalToolsSetting("PATH_SHELL"); + cmd[1] = "-c"; + cmd[2] = cmdString; + + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader input = new BufferedReader(new InputStreamReader(p + .getInputStream())); + while ((line = input.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + interfaces.add(new String[]{matcher.group(1), matcher.group(2)}); + } + } + input.close(); + + BufferedReader err = new BufferedReader(new InputStreamReader(p + .getErrorStream())); + if (err.ready()) + logger.warn("Error occured during scan:"); + while ((line = err.readLine()) != null) { + logger.warn(line); + } + err.close(); + } catch (IOException err) { + logger.fatal("Error while scanning for interfaces: " + err); + err.printStackTrace(); + } catch (Exception err) { + logger.fatal("Error while scanning for interfaces: " + err); + err.printStackTrace(); + } + return interfaces; + } + + private void autoSelectDependencyProcesses(String processName, + String sourceFilename, boolean confirmSelection) { + + /* + * Try to autofind and select dependency processes (via + * AUTOSTART_PROCESSES()) If this procedure fails, nothing will be reported + * to user + */ + + // Open source code file + FileReader reader = null; + try { + reader = new FileReader(textCoreDir.getText() + File.separatorChar + + sourceFilename); + } catch (Exception ex) { + } + for (File userPlatform : GUI.currentGUI.getUserPlatforms()) { + if (reader == null) { + try { + reader = new FileReader(userPlatform.getPath() + File.separatorChar + + sourceFilename); + } catch (Exception ex) { + } + } + } + for (File userPlatform : moteTypeUserPlatforms) { + if (reader == null) { + try { + reader = new FileReader(userPlatform.getPath() + File.separatorChar + + sourceFilename); + } catch (Exception ex) { + } + } + } + if (reader == null) { + // Could not locate source file + return; + } + + // Find which processes were set to autostart + BufferedReader sourceFile = new BufferedReader(reader); + String line; + try { + String autostartExpression = "^AUTOSTART_PROCESSES([^$]*)$"; + + Pattern pattern = Pattern.compile(autostartExpression); + + while ((line = sourceFile.readLine()) != null) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + // Parse autostart processes + String[] allProcesses = matcher.group(1).split(","); + for (String autostartProcess : allProcesses) { + autostartProcess = autostartProcess.replaceAll("[&; \\(\\)]", ""); + + // Does this process already exist? + boolean processAlreadyExists = false; + for (Component checkBox : processPanel.getComponents()) { + String existingProcess = ((JCheckBox) checkBox).getText(); + if (existingProcess.equals(autostartProcess)) { + processAlreadyExists = true; + } + } + + if (!processAlreadyExists) { + + if (confirmSelection) { + Object[] options = {"Create", "Cancel"}; + + String question = "The process [PROC1] depends on the following process: [PROC2]\nDo you want to add this as well?"; + String title = "Add dependency process?"; + question = question.replaceFirst("\\[PROC1\\]", processName); + question = question.replaceFirst("\\[PROC2\\]", + autostartProcess); + int answer = JOptionPane.showOptionDialog(myDialog, question, + title, JOptionPane.DEFAULT_OPTION, + JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + + if (answer == JOptionPane.YES_OPTION) { + JCheckBox processCheckBox = new JCheckBox(autostartProcess, + true); + processCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + processCheckBox.setActionCommand("process_clicked: " + + autostartProcess); + processCheckBox.addActionListener(myEventHandler); + + processPanel.add(processCheckBox); + myDialog.pack(); + } + } else { + JCheckBox processCheckBox = new JCheckBox(autostartProcess, + true); + processCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + processCheckBox.setActionCommand("process_clicked: " + + autostartProcess); + processCheckBox.addActionListener(myEventHandler); + + processPanel.add(processCheckBox); + myDialog.pack(); + } + } + } + + } + } + } catch (Exception ex) { + return; + } + } + + private void pathsWereUpdated() { + updateVisualFields(); + + // Remove all prevously scanned entries + coreInterfacePanel.removeAll(); + moteInterfacePanel.removeAll(); + processPanel.removeAll(); + sensorPanel.removeAll(); + coreInterfacePanel.repaint(); + moteInterfacePanel.repaint(); + processPanel.repaint(); + sensorPanel.repaint(); + createButton.setEnabled(libraryCreatedOK = false); + + settingsOK = false; + testButton.setEnabled(settingsOK); + } + + private void updateVisualFields() { + settingsOK = true; + + // Check for non-unique identifier + textID.setBackground(Color.WHITE); + textID.setToolTipText(null); + + for (MoteType otherType : allOtherTypes) { + if (otherType != myMoteType + && otherType.getIdentifier().equalsIgnoreCase(textID.getText())) { + textID.setBackground(Color.RED); + textID.setToolTipText("Conflicting name - must be unique"); + settingsOK = false; + break; + } + } + + // Check for non-unique description + textDescription.setBackground(Color.WHITE); + textDescription.setToolTipText(null); + + for (MoteType otherType : allOtherTypes) { + if (otherType != myMoteType + && otherType.getDescription().equals(textDescription.getText())) { + textDescription.setBackground(Color.RED); + textDescription.setToolTipText("Conflicting name - must be unique"); + settingsOK = false; + break; + } + } + + // Warn if spaces in Contiki path + textContikiDir.setBackground(Color.WHITE); + textContikiDir.setToolTipText(null); + if (textContikiDir.getText().contains(" ")) { + textContikiDir.setBackground(Color.ORANGE); + textContikiDir + .setToolTipText("Compilation may not work correctly with spaced paths"); + } + textCoreDir.setText(textContikiDir.getText() + + GUI.getExternalToolsSetting("PATH_COOJA_CORE_RELATIVE")); + textCoreDir.setBackground(Color.WHITE); + textCoreDir.setToolTipText(null); + if (textCoreDir.getText().contains(" ")) { + textCoreDir.setBackground(Color.ORANGE); + textCoreDir + .setToolTipText("Compilation may not work correctly with spaced paths"); + } + + // Warn if spaces in a user platform path + textUserPlatforms.setBackground(Color.WHITE); + textUserPlatforms.setToolTipText(null); + for (File userPlatform : moteTypeUserPlatforms) { + if (userPlatform.getPath().contains(" ")) { + textUserPlatforms.setBackground(Color.ORANGE); + textUserPlatforms + .setToolTipText("Compilation may not work correctly with spaced paths"); + } + } + + // Update output text field + textOutputFiles.setText(ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.mapSuffix + + ", " + ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.librarySuffix + + ", " + ContikiMoteType.tempOutputDirectory.getPath() + + File.separatorChar + textID.getText() + ContikiMoteType.dependSuffix); + + createButton.setEnabled(libraryCreatedOK = false); + testButton.setEnabled(settingsOK); + } + + /** + * Scans Contiki base + (optional) user platform for Contiki processes, + * sensors and core interfaces. The new mote type config is recreated every + * time this method is run. If a user platform is specified, it looks for a + * special class config file there, and appends it to the new mote type + * config. By reading that config all available mote interfaces are parsed - + * which will all be selected initially. This method also selects the core + * interfaces needed by the mote interfaces. + * + * Finally any pre-specified processes (via shortcut start) will be selected. + */ + private void rescanDirectories() { + + boolean pathErrorFound = false; + + // Check that Contiki path is correct + if (!new File(myDialog.textContikiDir.getText()).isDirectory()) { + // Contiki path specified does not exist + textContikiDir.setBackground(Color.RED); + textContikiDir.setToolTipText("Incorrect path"); + pathErrorFound = true; + } + + // Check that cooja main platform path is correct + if (!new File(myDialog.textCoreDir.getText()).isDirectory()) { + // Cooja main platform specified does not exist + textContikiDir.setBackground(Color.RED); + textContikiDir.setToolTipText("Incorrect path"); + textCoreDir.setBackground(Color.RED); + textCoreDir.setToolTipText("Incorrect path"); + pathErrorFound = true; + } + + // Check that all user platforms are valid + for (File userPlatform : moteTypeUserPlatforms) { + File userPlatformConfig = new File(userPlatform.getPath() + + File.separatorChar + GUI.PLATFORM_CONFIG_FILENAME); + if (!userPlatformConfig.exists()) { + textUserPlatforms.setBackground(Color.RED); + textUserPlatforms.setToolTipText("Invalid user platform: " + + userPlatform); + pathErrorFound = true; + } + } + + if (pathErrorFound) { + // Remove all prevously scanned entries + coreInterfacePanel.removeAll(); + processPanel.removeAll(); + sensorPanel.removeAll(); + coreInterfacePanel.repaint(); + processPanel.repaint(); + sensorPanel.repaint(); + testButton.setEnabled(settingsOK = false); + createButton.setEnabled(libraryCreatedOK = false); + return; + } + + // Scan for mote interfaces (+ recreate node type class configuration) + myEventHandler.actionPerformed(new ActionEvent(myDialog.createButton, + ActionEvent.ACTION_PERFORMED, "scanmoteinterfaces")); + + // Scan for processes + myEventHandler.actionPerformed(new ActionEvent(myDialog.createButton, + ActionEvent.ACTION_PERFORMED, "scanprocesses")); + + // Scan for sensors + myDialog.myEventHandler.actionPerformed(new ActionEvent( + myDialog.createButton, ActionEvent.ACTION_PERFORMED, "scansensors")); + + // Scan for core interfaces + myDialog.myEventHandler.actionPerformed(new ActionEvent( + myDialog.createButton, ActionEvent.ACTION_PERFORMED, + "scancoreinterfaces")); + + // Recheck dependencies between selected mote interfaces and available + // core interfaces + myDialog.myEventHandler.actionPerformed(new ActionEvent( + myDialog.createButton, ActionEvent.ACTION_PERFORMED, + "recheck_interface_dependencies")); + + // Check if COOJA was started with a recommended process (select process) + if (System.getProperty("QUICKSTART_APP") != null) { + + // Pre-select recommended process + String wantedSourceFilename = System.getProperty("QUICKSTART_APP"); + + for (Component checkBox : myDialog.processPanel.getComponents()) { + JCheckBox processCheckBox = (JCheckBox) checkBox; + String processFilename = processCheckBox.getToolTipText(); + + if (processFilename.startsWith(wantedSourceFilename)) { + // We found the recommended process, select it + processCheckBox.setSelected(true); + myDialog.autoSelectDependencyProcesses(processCheckBox.getText(), + processFilename, false); + break; + } + } + } + + settingsOK = true; + testButton.setEnabled(settingsOK); + } + + private class MoteTypeEventHandler + implements + ActionListener, + DocumentListener { + public void insertUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void removeUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void changedUpdate(DocumentEvent e) { + if (myDialog.isVisible()) + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + pathsWereUpdated(); + } + }); + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("cancel")) { + // Cancel creation of mote type + myMoteType = null; + dispose(); + } else if (e.getActionCommand().equals("clean")) { + // Delete any created intermediate files + File objectDir = new File("obj_cooja"); + if (objectDir.exists() && objectDir.isDirectory()) { + File[] objectFiles = objectDir.listFiles(); + for (File objectFile : objectFiles) + objectFile.delete(); + + objectDir.delete(); + } + } else if (e.getActionCommand().equals("create")) { + // Create mote type and set various data + myMoteType.doInit(textID.getText()); + myMoteType.setDescription(textDescription.getText()); + myMoteType.setContikiBaseDir(textContikiDir.getText()); + myMoteType.setContikiCoreDir(textCoreDir.getText()); + myMoteType.setUserPlatformDirs(moteTypeUserPlatforms); + myMoteType.setConfig(newMoteTypeConfig); + + // Set startup processes + Vector processes = new Vector(); + for (Component checkBox : processPanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + processes.add(((JCheckBox) checkBox).getText()); + } + } + myMoteType.setProcesses(processes); + + // Set registered sensors + Vector sensors = new Vector(); + for (Component checkBox : sensorPanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + sensors.add(((JCheckBox) checkBox).getText()); + } + } + myMoteType.setSensors(sensors); + + // Set registered core interfaces + Vector coreInterfaces = new Vector(); + for (Component checkBox : coreInterfacePanel.getComponents()) { + if (((JCheckBox) checkBox).isSelected()) { + coreInterfaces.add(((JCheckBox) checkBox).getText()); + } + } + myMoteType.setCoreInterfaces(coreInterfaces); + + // Set registered mote interfaces + Vector> moteInterfaces = new Vector>(); + for (Component checkBox : moteInterfacePanel.getComponents()) { + JCheckBox interfaceCheckBox = (JCheckBox) checkBox; + if (interfaceCheckBox.isSelected()) { + moteInterfaces + .add((Class) interfaceCheckBox + .getClientProperty("class")); + } + } + myMoteType.setMoteInterfaces(moteInterfaces); + + dispose(); + } else if (e.getActionCommand().equals("testsettings")) { + testButton.requestFocus(); + Thread testSettingsThread = new Thread(new Runnable() { + public void run() { + doTestSettings(); + } + }, "test settings thread"); + testSettingsThread.start(); + } else if (e.getActionCommand().equals("browsecontiki")) { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new java.io.File(".")); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setDialogTitle("Contiki OS base directory"); + + if (fc.showOpenDialog(myDialog) == JFileChooser.APPROVE_OPTION) { + textContikiDir.setText(fc.getSelectedFile().getPath()); + } + createButton.setEnabled(libraryCreatedOK = false); + pathsWereUpdated(); + } else if (e.getActionCommand().equals("manageuserplatforms")) { + Vector newPlatforms = UserPlatformsDialog.showDialog( + myParentFrame, moteTypeUserPlatforms, GUI.currentGUI + .getUserPlatforms()); + if (newPlatforms != null) { + moteTypeUserPlatforms = newPlatforms; + String userPlatformText = null; + for (File userPlatform : newPlatforms) { + if (userPlatformText == null) + userPlatformText = "'" + userPlatform.getPath() + "'"; + else + userPlatformText += " + '" + userPlatform.getPath() + "'"; + } + textUserPlatforms.setText(userPlatformText); + + createButton.setEnabled(libraryCreatedOK = false); + pathsWereUpdated(); + } + } else if (e.getActionCommand().equals("scanprocesses")) { + // Clear process panel + processPanel.removeAll(); + Vector processes = new Vector(); + + // Scan core platform for processes + processes.addAll(ContikiMoteTypeDialog.scanForProcesses(new File( + textCoreDir.getText()))); + + // If user platforms exists, scan those too + for (File userPlatform : GUI.currentGUI.getUserPlatforms()) { + processes.addAll(ContikiMoteTypeDialog + .scanForProcesses(userPlatform)); + } + if (moteTypeUserPlatforms != null) { + for (File userPlatform : moteTypeUserPlatforms) { + processes.addAll(ContikiMoteTypeDialog + .scanForProcesses(userPlatform)); + } + } + + if (processes != null) { + for (String[] processInfo : processes) { + JCheckBox processCheckBox = new JCheckBox(processInfo[1], false); + processCheckBox.setToolTipText(processInfo[0]); + processCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + processCheckBox.setActionCommand("process_clicked: " + + processInfo[1]); + processCheckBox.addActionListener(myEventHandler); + + processPanel.add(processCheckBox); + } + } else { + logger.warn("No processes found during scan"); + testButton.setEnabled(settingsOK = false); + } + + processPanel.repaint(); + myDialog.pack(); + createButton.setEnabled(libraryCreatedOK = false); + + } else if (e.getActionCommand().equals("scansensors")) { + // Clear sensor panel + sensorPanel.removeAll(); + Vector sensors = new Vector(); + + // Scan core platform for sensors + sensors.addAll(ContikiMoteTypeDialog.scanForSensors(new File( + textCoreDir.getText()))); + + // If user platforms exists, scan those too + for (File userPlatform : GUI.currentGUI.getUserPlatforms()) { + sensors.addAll(ContikiMoteTypeDialog.scanForSensors(userPlatform)); + } + if (moteTypeUserPlatforms != null) { + for (File userPlatform : moteTypeUserPlatforms) { + sensors.addAll(ContikiMoteTypeDialog.scanForSensors(userPlatform)); + } + } + + if (sensors != null) { + for (String[] sensorInfo : sensors) { + JCheckBox sensorCheckBox = new JCheckBox(sensorInfo[1], true); + sensorCheckBox.setToolTipText(sensorInfo[0]); + sensorCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + sensorPanel.add(sensorCheckBox); + } + } else { + logger.warn("No sensors found during scan"); + testButton.setEnabled(settingsOK = false); + } + + sensorPanel.repaint(); + myDialog.pack(); + createButton.setEnabled(libraryCreatedOK = false); + } else if (e.getActionCommand().equals("scancoreinterfaces")) { + // Clear core interface panel + coreInterfacePanel.removeAll(); + Vector interfaces = new Vector(); + + // Scan core platform for core interfaces + interfaces.addAll(ContikiMoteTypeDialog.scanForInterfaces(new File( + textCoreDir.getText()))); + + // If user platforms exists, scan those too + for (File userPlatform : GUI.currentGUI.getUserPlatforms()) { + interfaces.addAll(ContikiMoteTypeDialog + .scanForInterfaces(userPlatform)); + } + if (moteTypeUserPlatforms != null) { + for (File userPlatform : moteTypeUserPlatforms) { + interfaces.addAll(ContikiMoteTypeDialog + .scanForInterfaces(userPlatform)); + } + } + + if (interfaces != null) { + for (String[] interfaceInfo : interfaces) { + JCheckBox interfaceCheckBox = new JCheckBox(interfaceInfo[1], false); + interfaceCheckBox.setToolTipText(interfaceInfo[0]); + interfaceCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + coreInterfacePanel.add(interfaceCheckBox); + } + } else { + logger.warn("No core interfaces found during scan"); + testButton.setEnabled(settingsOK = false); + } + recheckInterfaceDependencies(); + + coreInterfacePanel.repaint(); + myDialog.pack(); + createButton.setEnabled(libraryCreatedOK = false); + + } else if (e.getActionCommand().equals("scanmoteinterfaces")) { + // Clear core interface panel + moteInterfacePanel.removeAll(); + + // Clone general simulator config + newMoteTypeConfig = GUI.currentGUI.getPlatformConfig().clone(); + + // Merge with all user platform configs (if any) + for (File userPlatform : moteTypeUserPlatforms) { + File userPlatformConfig = new File(userPlatform.getPath() + + File.separatorChar + GUI.PLATFORM_CONFIG_FILENAME); + if (userPlatformConfig.exists()) { + try { + newMoteTypeConfig.appendConfig(userPlatformConfig); + } catch (Exception ex) { + logger.fatal("Error when parsing user platform config: " + ex); + return; + } + } else + logger.fatal("Could not find user platform config file: " + + userPlatformConfig); + } + + // Get all mote interfaces available from config + String[] moteInterfaces = newMoteTypeConfig.getStringArrayValue( + ContikiMoteType.class, "MOTE_INTERFACES"); + Vector> moteIntfClasses = new Vector>(); + + // Find and load the mote interface classes + for (String moteInterface : moteInterfaces) { + + Class newMoteInterfaceClass = GUI.currentGUI.tryLoadClass(this, MoteInterface.class, + moteInterface); + + if (newMoteInterfaceClass == null) { + logger.warn("Failed to load mote interface: " + moteInterface); + } else { + moteIntfClasses.add(newMoteInterfaceClass); + //logger.info("Loaded mote interface: " + newMoteInterfaceClass); + } + } + + // Create and add checkboxes for all mote interfaces + if (moteIntfClasses.size() > 0) { + for (Class moteIntfClass : moteIntfClasses) { + JCheckBox interfaceCheckBox = new JCheckBox(GUI + .getDescriptionOf(moteIntfClass), true); + interfaceCheckBox.putClientProperty("class", moteIntfClass); + interfaceCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + + interfaceCheckBox + .setActionCommand("recheck_interface_dependencies"); + interfaceCheckBox.addActionListener(myEventHandler); + + moteInterfacePanel.add(interfaceCheckBox); + } + } else { + logger + .warn("Error occured during parsing of mote interfaces (no found)"); + testButton.setEnabled(settingsOK = false); + } + + moteInterfacePanel.repaint(); + myDialog.pack(); + createButton.setEnabled(libraryCreatedOK = false); + } else if (e.getActionCommand().equals("addprocess")) { + String newProcessName = JOptionPane.showInputDialog(myDialog, + "Enter process name"); + if (newProcessName != null) { + JCheckBox processCheckBox = new JCheckBox(newProcessName, false); + processCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + processCheckBox.setSelected(true); + + processCheckBox + .setActionCommand("process_clicked: " + newProcessName); + processCheckBox.addActionListener(myEventHandler); + + processPanel.add(processCheckBox); + } + myDialog.pack(); + } else if (e.getActionCommand().equals("addsensor")) { + String newSensorName = JOptionPane.showInputDialog(myDialog, + "Enter sensor name"); + if (newSensorName != null) { + JCheckBox sensorCheckBox = new JCheckBox(newSensorName, false); + sensorCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + sensorCheckBox.setSelected(true); + sensorPanel.add(sensorCheckBox); + } + myDialog.pack(); + } else if (e.getActionCommand().equals("addinterface")) { + String newInterfaceName = JOptionPane.showInputDialog(myDialog, + "Enter interface name"); + if (newInterfaceName != null) { + JCheckBox interfaceCheckBox = new JCheckBox(newInterfaceName, false); + interfaceCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + interfaceCheckBox.setSelected(true); + coreInterfacePanel.add(interfaceCheckBox); + } + myDialog.pack(); + } else if (e.getActionCommand().equals("recheck_interface_dependencies")) { + recheckInterfaceDependencies(); + } else if (e.getActionCommand().startsWith("process_clicked")) { + + boolean processWasSelected = false; + String sourceFilename = null; + String processName = e.getActionCommand().split(": ")[1].trim(); + + // Was the process selected or unselected? + for (Component checkBox : processPanel.getComponents()) { + JCheckBox processCheckbox = (JCheckBox) checkBox; + if (processCheckbox.isSelected() + && processCheckbox.getText().equals(processName)) { + processWasSelected = true; + sourceFilename = ((JCheckBox) checkBox).getToolTipText(); + break; + } + } + + if (!processWasSelected || sourceFilename == null) + return; + + autoSelectDependencyProcesses(processName, sourceFilename, true); + + } else + logger.warn("Unhandled action: " + e.getActionCommand()); + + createButton.setEnabled(libraryCreatedOK = false); + + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiBeeper.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiBeeper.java new file mode 100644 index 000000000..d1ff84698 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiBeeper.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiBeeper.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.util.Collection; +import javax.swing.JPanel; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.Beeper; + +/** + * Ths class represents a beeper. + * + * It needs read access to the following core variables: + *

    + *
  • char simBeeped (1=on, else off) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • beep_interface + *
+ *

+ * This observable is changed and notifies observers whenever the beeper changes + * state. + * + * @author Fredrik Osterlind + */ +public class ContikiBeeper extends Beeper implements ContikiMoteInterface { + private Mote mote = null; + private SectionMoteMemory moteMem = null; + private static Logger logger = Logger.getLogger(ContikiBeeper.class); + + /** + * Assuming beep always lasts for 0.1 seconds. ESB measured energy + * consumption: 16.69 mA. Total energy consumption of a beep is then: + * 0.1*16.69 + */ + private final double ENERGY_CONSUMPTION_BEEP; + + private double myEnergyConsumption = 0.0; + + private boolean justBeeped = false; + + /** + * Creates an interface to the beeper at mote. + * + * @param mote + * Beeper's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiBeeper(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_BEEP = mote.getType().getConfig().getDoubleValue( + ContikiBeeper.class, "BEEP_CONSUMPTION_mQ"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + } + + public boolean isBeeping() { + return justBeeped; + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"beep_interface"}; + } + + public void doActionsBeforeTick() { + // Nothing to do + justBeeped = false; + } + + public void doActionsAfterTick() { + if (moteMem.getByteValueOf("simBeeped") == 1) { + this.setChanged(); + this.notifyObservers(mote); + justBeeped = true; + moteMem.setByteValueOf("simBeeped", (byte) 0); + myEnergyConsumption = ENERGY_CONSUMPTION_BEEP; + } else + myEnergyConsumption = 0.0; + } + + public JPanel getInterfaceVisualizer() { + return null; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiButton.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiButton.java new file mode 100644 index 000000000..9e6cd6d17 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiButton.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiButton.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.event.*; +import java.util.Collection; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.Button; + +/** + * This class represents a button. + * + * It needs read/write access to the following core variables: + *

    + *
  • char simButtonIsDown (1=down, else up) + *
  • char simButtonChanged (1=changed, else not changed) + *
  • char simButtonIsActive (1=active, else inactive) + *
+ * Dependency core interfaces are: + *
    + *
  • button_interface + *
+ *

+ * This observable notifies observers when the button changes state (between + * pressed and released). + * + * @author Fredrik Osterlind + */ +public class ContikiButton extends Button implements ContikiMoteInterface { + private SectionMoteMemory moteMem; + private Mote mote; + + private boolean shouldBeReleased = false; + private static Logger logger = Logger.getLogger(ContikiButton.class); + + private final boolean RAISES_EXTERNAL_INTERRUPT; + + /** + * Creates an interface to the button at mote. + * + * @param mote + * Button's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiButton(Mote mote) { + // Read class configurations of this mote type + RAISES_EXTERNAL_INTERRUPT = mote.getType().getConfig() + .getBooleanValue(ContikiButton.class, "EXTERNAL_INTERRUPT_bool"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"button_interface"}; + } + + /** + * Clicks button. Button will be down for one tick, and then released. + */ + public void clickButton() { + pressButton(); + shouldBeReleased = true; + } + + public void releaseButton() { + moteMem.setByteValueOf("simButtonIsDown", (byte) 0); + + if (moteMem.getByteValueOf("simButtonIsActive") == 1) { + moteMem.setByteValueOf("simButtonChanged", (byte) 1); + + // If mote is inactive, wake it up + if (RAISES_EXTERNAL_INTERRUPT) + mote.setState(Mote.STATE_ACTIVE); + + setChanged(); + notifyObservers(); + } + } + + public void pressButton() { + moteMem.setByteValueOf("simButtonIsDown", (byte) 1); + + if (moteMem.getByteValueOf("simButtonIsActive") == 1) { + moteMem.setByteValueOf("simButtonChanged", (byte) 1); + + // If mote is inactive, wake it up + if (RAISES_EXTERNAL_INTERRUPT) + mote.setState(Mote.STATE_ACTIVE); + + setChanged(); + notifyObservers(); + } + } + + public boolean isPressed() { + return moteMem.getByteValueOf("simButtonIsDown") == 1; + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + // If a button is pressed and should be clicked, release it now + if (shouldBeReleased) { + releaseButton(); + shouldBeReleased = false; + } + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + final JButton clickButton = new JButton("Click button"); + + panel.add(clickButton); + + clickButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + clickButton(); + } + }); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return 0.0; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiClock.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiClock.java new file mode 100644 index 000000000..b3577c464 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiClock.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiClock.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.util.Collection; +import javax.swing.JPanel; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.Clock; + +/** + * The class represents a clock and controls the core time. + * + * It needs read/write access to the following core variables: + *

    + *
  • int simCurrentTime + *
+ * Dependency core interfaces are: + *
    + *
  • clock_interface + *
+ *

+ * This observable never updates. + * + * @author Fredrik Osterlind + */ +public class ContikiClock extends Clock implements ContikiMoteInterface { + + private Mote mote = null; + private SectionMoteMemory moteMem = null; + + private int timeDrift = 0; + + /** + * Creates a time connected to mote. + * + * @param mote + * Mote to connect this time to. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiClock(Mote mote) { + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"clock_interface"}; + } + + public void setTime(int newTime) { + moteMem.setIntValueOf("simCurrentTime", newTime); + } + + public int getTime() { + return moteMem.getIntValueOf("simCurrentTime"); + } + + public void doActionsBeforeTick() { + // Update core time to correspond with the simulation time + setTime((int) mote.getSimulation().getSimulationTime() + timeDrift); + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + return null; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return 0.0; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiIPAddress.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiIPAddress.java new file mode 100644 index 000000000..ccc52fc1e --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiIPAddress.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiIPAddress.java,v 1.1 2006/08/21 12:13:04 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.IPAddress; + +/** + * This class represents an uIP IP address. + * + * It needs write access to the following core variables: + *

    + *
  • char simIPa + *
  • char simIPb + *
  • char simIPc + *
  • char simIPd + *
  • char simIPChanged (1 if new IP should be set) + *
+ *

+ * The new IP will be "simIPa.simIPb.simIPc.simIPd". Dependency core interfaces + * are: + *

    + *
  • ip_interface + *
+ * + * This observable notifies observers if the IP address is set or changed. + * + * @author Fredrik Osterlind + */ +public class ContikiIPAddress extends IPAddress implements ContikiMoteInterface { + private SectionMoteMemory moteMem = null; + private static Logger logger = Logger.getLogger(ContikiIPAddress.class); + private boolean setIP; + + char a = 0, b = 0, c = 0, d = 0; + + /** + * Creates an interface to the IP address at mote. + * + * @param mote + * IP address' mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiIPAddress(Mote mote) { + this.moteMem = (SectionMoteMemory) mote.getMemory(); + setIP = false; + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"ip_interface"}; + } + + public String getIPString() { + return "" + (int) a + "." + (int) b + "." + (int) c + "." + (int) d; + } + + public void setIPString(String ipAddress) { + String[] ipArray = ipAddress.split("\\."); + if (ipArray.length < 4) { + logger.warn("Could not set ip address (" + ipAddress + ")"); + } else + setIPNumber((char) Integer.parseInt(ipArray[0]), (char) Integer + .parseInt(ipArray[1]), (char) Integer.parseInt(ipArray[2]), + (char) Integer.parseInt(ipArray[3])); + } + + public void setIPNumber(char a, char b, char c, char d) { + setIP = true; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + } + + public void doActionsBeforeTick() { + if (setIP) { + setIP = false; + moteMem.setByteValueOf("simIPa", (byte) a); + moteMem.setByteValueOf("simIPb", (byte) b); + moteMem.setByteValueOf("simIPc", (byte) c); + moteMem.setByteValueOf("simIPd", (byte) d); + moteMem.setByteValueOf("simIPChanged", (byte) 1); + + setChanged(); + notifyObservers(); + } + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + final JLabel ipLabel = new JLabel(); + + ipLabel.setText("Current address: " + (int) a + "." + (int) b + "." + + (int) c + "." + (int) d); + + panel.add(ipLabel); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + ipLabel.setText("Current address: " + (int) a + "." + (int) b + "." + + (int) c + "." + (int) d); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + // Virtual interface, does not require any energy + return 0.0; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // Infinite boolean + element = new Element("ipv4address"); + element.setText(getIPString()); + config.add(element); + + return config; + } + + public void setConfigXML(Collection configXML) { + for (Element element : configXML) { + if (element.getName().equals("ipv4address")) { + setIPString(element.getText()); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLED.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLED.java new file mode 100644 index 000000000..13df38ea4 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLED.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiLED.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.*; +import java.util.*; +import javax.swing.JPanel; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.LED; + +/** + * This class represents three mote LEDs. + * + * It needs read access to the following core variables: + *
    + *
  • char simLedsValue + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • leds_interface + *
+ *

+ * This observable is changed and notifies observers whenever any led has + * changed. + * + * @author Fredrik Osterlind + */ +public class ContikiLED extends LED implements ContikiMoteInterface { + private static Logger logger = Logger.getLogger(ContikiLED.class); + private Mote mote = null; + private SectionMoteMemory moteMem = null; + private byte oldLedValue = 0; + + private static final byte LEDS_GREEN = 1; + private static final byte LEDS_YELLOW = 2; + private static final byte LEDS_RED = 4; + + private static final Color DARK_GREEN = new Color(0, 100, 0); + private static final Color DARK_YELLOW = new Color(100, 100, 0); + private static final Color DARK_RED = new Color(100, 0, 0); + private static final Color GREEN = new Color(0, 255, 0); + private static final Color YELLOW = new Color(255, 255, 0); + private static final Color RED = new Color(255, 0, 0); + + private double myEnergyConsumption = 0.0; + + /** + * Approximate energy consumption of a green led (mA). ESB measured value: + * 5.69 mA. TODO Measure energy consumption + */ + public final double ENERGY_CONSUMPTION_GREEN_LED; + + /** + * Approximate energy consumption of a yellow led (mA). ESB measured value: + * 5.69 mA. TODO Measure energy consumption + */ + public final double ENERGY_CONSUMPTION_YELLOW_LED; + + /** + * Approximate energy consumption of a red led (mA). ESB measured value: 5.69 + * mA. + */ + public final double ENERGY_CONSUMPTION_RED_LED; + + private double energyOfGreenLedPerTick = -1; + private double energyOfYellowLedPerTick = -1; + private double energyOfRedLedPerTick = -1; + + public ContikiLED() { + ENERGY_CONSUMPTION_GREEN_LED = 0; + ENERGY_CONSUMPTION_YELLOW_LED = 0; + ENERGY_CONSUMPTION_RED_LED = 0; + } + + /** + * Creates an interface to the led at mote. + * + * @param mote + * Led's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiLED(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_GREEN_LED = mote.getType().getConfig() + .getDoubleValue(ContikiLED.class, "GREEN_LED_CONSUMPTION_mA"); + ENERGY_CONSUMPTION_YELLOW_LED = mote.getType().getConfig() + .getDoubleValue(ContikiLED.class, "YELLOW_LED_CONSUMPTION_mA"); + ENERGY_CONSUMPTION_RED_LED = mote.getType().getConfig() + .getDoubleValue(ContikiLED.class, "RED_LED_CONSUMPTION_mA"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + + if (energyOfGreenLedPerTick < 0) { + energyOfGreenLedPerTick = ENERGY_CONSUMPTION_GREEN_LED + * mote.getSimulation().getTickTimeInSeconds(); + energyOfYellowLedPerTick = ENERGY_CONSUMPTION_YELLOW_LED + * mote.getSimulation().getTickTimeInSeconds(); + energyOfRedLedPerTick = ENERGY_CONSUMPTION_RED_LED + * mote.getSimulation().getTickTimeInSeconds(); + } + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"leds_interface"}; + } + + public boolean isAnyOn() { + return oldLedValue > 0; + } + + public boolean isGreenOn() { + return (oldLedValue & LEDS_GREEN) > 0; + } + + public boolean isYellowOn() { + return (oldLedValue & LEDS_YELLOW) > 0; + } + + public boolean isRedOn() { + return (oldLedValue & LEDS_RED) > 0; + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + if (checkLedStatus()) { + this.setChanged(); + this.notifyObservers(mote); + } + } + + private boolean checkLedStatus() { + boolean ledChanged; + + byte newLedsValue = moteMem.getByteValueOf("simLedsValue"); + if (newLedsValue != oldLedValue) { + ledChanged = true; + } else { + ledChanged = false; + } + + myEnergyConsumption = 0.0; + if ((newLedsValue & LEDS_GREEN) > 0) + myEnergyConsumption += energyOfGreenLedPerTick; + if ((newLedsValue & LEDS_YELLOW) > 0) + myEnergyConsumption += energyOfYellowLedPerTick; + if ((newLedsValue & LEDS_RED) > 0) + myEnergyConsumption += energyOfRedLedPerTick; + + oldLedValue = newLedsValue; + return ledChanged; + } + + public JPanel getInterfaceVisualizer() { + final JPanel panel = new JPanel() { + public void paintComponent(Graphics g) { + super.paintComponent(g); + + if (isGreenOn()) { + g.setColor(GREEN); + g.fillOval(20, 20, 20, 20); + } else { + g.setColor(DARK_GREEN); + g.fillOval(20, 20, 20, 20); + } + + if (isYellowOn()) { + g.setColor(YELLOW); + g.fillOval(60, 20, 20, 20); + } else { + g.setColor(DARK_YELLOW); + g.fillOval(60, 20, 20, 20); + } + + if (isRedOn()) { + g.setColor(RED); + g.fillOval(100, 20, 20, 20); + } else { + g.setColor(DARK_RED); + g.fillOval(100, 20, 20, 20); + } + } + }; + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + panel.repaint(); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + panel.setMinimumSize(new Dimension(140, 60)); + panel.setPreferredSize(new Dimension(140, 60)); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLog.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLog.java new file mode 100644 index 000000000..755a13e3a --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiLog.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiLog.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.Log; + + +/** + * The class Log is an abstract interface to a mote's logging output. It needs + * read access to the following core variables: + *

    + *
  • char simLoggedFlag + * (1=mote has new outgoing log messages, else no new) + *
  • int simLoggedLength + * (length of new log message) + *
  • byte[] simLoggedData (data of new log messages) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • simlog_interface + *
+ *

+ * This observable is changed and notifies observers whenever a log message has + * been received from the core (checked after each tick). The public method + * getLastLogMessages gives access to the last log message(s). + * + * @author Fredrik Osterlind + */ +public class ContikiLog extends Log implements ContikiMoteInterface { + private static Logger logger = Logger.getLogger(ContikiLog.class); + private Mote mote = null; + private SectionMoteMemory moteMem = null; + + private String lastLogMessage = null; + + /** + * Creates an interface to mote's logging output. + * + * @param mote Log's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiLog(Mote mote) { + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[] { "simlog_interface" }; + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + if (moteMem.getByteValueOf("simLoggedFlag") == 1) { + int totalLength = moteMem.getIntValueOf("simLoggedLength"); + byte[] bytes = moteMem.getByteArray("simLoggedData", totalLength); + char[] chars = new char[bytes.length]; + for (int i=0; i < chars.length; i++) + chars[i] = (char) bytes[i]; + + String message = String.valueOf(chars); + lastLogMessage = message; + + moteMem.setByteValueOf("simLoggedFlag", (byte) 0); + moteMem.setIntValueOf("simLoggedLength", (int) 0); + + this.setChanged(); + this.notifyObservers(mote); + } + } + + public String getLastLogMessages() { + return lastLogMessage; + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + final JTextArea logTextPane = new JTextArea(); + logTextPane.setOpaque(false); + logTextPane.setEditable(false); + + if (lastLogMessage == null) + logTextPane.setText(""); + else + logTextPane.append(lastLogMessage); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + logTextPane.append(lastLogMessage); + logTextPane.setCaretPosition(logTextPane.getDocument().getLength()); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + JScrollPane scrollPane = new JScrollPane(logTextPane); + scrollPane.setPreferredSize(new Dimension(100,100)); + panel.add(BorderLayout.NORTH, new JLabel("Last log messages:")); + panel.add(BorderLayout.CENTER, scrollPane); + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + // Does not require energy + return 0.0; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiMoteID.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiMoteID.java new file mode 100644 index 000000000..f080c6746 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiMoteID.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiMoteID.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.MoteID; + +/** + * Ths class represents a mote ID number. + * + * It needs write access to the following core variables: + *

    + *
  • int simMoteID + *
  • char simMoteIDChanged + *
+ * When the mote id is set or changed, the random generator will also be seeded + * with this id (core function random_init()). + *

+ * Dependency core interfaces are: + *

    + *
  • moteid_interface + *
+ * This observable notifies observers when the mote ID is set or changed. + * + * @author Fredrik Osterlind + */ +public class ContikiMoteID extends MoteID implements ContikiMoteInterface { + private SectionMoteMemory moteMem = null; + private static Logger logger = Logger.getLogger(ContikiMoteID.class); + private boolean setMoteID; + + int moteID = 0; + + /** + * Creates an interface to the mote ID at mote. + * + * @param mote + * Mote ID's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiMoteID(Mote mote) { + this.moteMem = (SectionMoteMemory) mote.getMemory(); + setMoteID = false; + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"moteid_interface"}; + } + + public int getMoteID() { + return moteID; + } + + public void setMoteID(int newID) { + setMoteID = true; + moteID = newID; + + setChanged(); + notifyObservers(); + } + + public void doActionsBeforeTick() { + if (setMoteID) { + setMoteID = false; + moteMem.setIntValueOf("simMoteID", moteID); + moteMem.setByteValueOf("simMoteIDChanged", (byte) 1); + } + setChanged(); + notifyObservers(); + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + final JLabel idLabel = new JLabel(); + + idLabel.setText("Mote ID: " + moteID); + + panel.add(idLabel); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + idLabel.setText("Mote ID: " + moteID); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return 0.0; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // Infinite boolean + element = new Element("id"); + element.setText(Integer.toString(moteID)); + config.add(element); + + return config; + } + + public void setConfigXML(Collection configXML) { + for (Element element : configXML) { + if (element.getName().equals("id")) { + setMoteID(Integer.parseInt(element.getText())); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiPIR.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiPIR.java new file mode 100644 index 000000000..13cc4d032 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiPIR.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiPIR.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.event.*; +import java.util.Collection; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.PIR; + +/** + * This class represents a passive infrared sensor. + * + * It needs read/write access to the following core variables: + *
    + *
  • char simPirChanged (1=changed, else not changed) + *
  • char simPirIsActive (1=active, else inactive) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • pir_interface + *
+ *

+ * This observable notifies observers if a change is discovered by the PIR. + * + * @author Fredrik Osterlind + */ +public class ContikiPIR extends PIR implements ContikiMoteInterface { + private static Logger logger = Logger.getLogger(ContikiPIR.class); + + /** + * Approximate energy consumption of an active PIR sensor. ESB measured energy + * consumption is 0.4 mA. TODO Measure energy consumption + */ + public final double ENERGY_CONSUMPTION_PIR_mA; + + private final boolean RAISES_EXTERNAL_INTERRUPT; + + private double energyActivePerTick = -1; + + private Mote mote; + private SectionMoteMemory moteMem; + private double myEnergyConsumption = 0.0; + + /** + * Creates an interface to the pir at mote. + * + * @param mote + * Button's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiPIR(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_PIR_mA = mote.getType().getConfig().getDoubleValue( + ContikiPIR.class, "ACTIVE_CONSUMPTION_mA"); + RAISES_EXTERNAL_INTERRUPT = mote.getType().getConfig() + .getBooleanValue(ContikiPIR.class, "EXTERNAL_INTERRUPT_bool"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + + if (energyActivePerTick < 0) + energyActivePerTick = ENERGY_CONSUMPTION_PIR_mA + * mote.getSimulation().getTickTimeInSeconds(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"pir_interface"}; + } + + public void triggerChange() { + if (moteMem.getByteValueOf("simPirIsActive") == 1) { + moteMem.setByteValueOf("simPirChanged", (byte) 1); + + // If mote is inactive, wake it up + if (RAISES_EXTERNAL_INTERRUPT) + mote.setState(Mote.STATE_ACTIVE); + + this.setChanged(); + this.notifyObservers(); + } + } + + public void doActionsBeforeTick() { + if (moteMem.getByteValueOf("simPirIsActive") == 1) { + myEnergyConsumption = energyActivePerTick; + } else + myEnergyConsumption = 0.0; + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + final JButton clickButton = new JButton("Signal PIR"); + + panel.add(clickButton); + + clickButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + triggerChange(); + } + }); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRS232.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRS232.java new file mode 100644 index 000000000..6ed40450d --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRS232.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiRS232.java,v 1.1 2006/08/21 12:13:04 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; + +/** + * The class represents a simple RS232/Serial interface (only shows String + * format). + * + * It needs read/write access to the following core variables: + *

    + *
  • char simSerialSendingFlag (1=mote is sending data) + *
  • int simSerialSendingLength (length of data being sent from mote) + *
  • byte[] simSerialSendingData (data being sent from mote) + *
  • char simSerialRecevingFlag (1=mote is receving data) + *
  • int simSerialReceivingLength (length of data being received at mote) + *
  • byte[] simSerialReceivingData (data being received at mote) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • rs232_interface + *
+ *

+ * This observable is changed and notifies observers whenever a serial message + * has been received from the core (checked after each tick). The public method + * getSerialMessages gives access to the last data. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Serial port (RS232)") +public class ContikiRS232 extends MoteInterface implements ContikiMoteInterface { + private static Logger logger = Logger.getLogger(ContikiRS232.class); + + private Mote mote = null; + private SectionMoteMemory moteMem = null; + + private String lastSerialMessage = null; + + private final boolean RAISES_EXTERNAL_INTERRUPT; + private JTextArea logTextPane = null; + + /** + * Approximate energy consumption of every sent character over RS232 (mQ). + */ + public final double ENERGY_CONSUMPTION_PER_CHAR_mQ; + + private double myEnergyConsumption = 0.0; + + /** + * Creates an interface to the RS232 at mote. + * + * @param mote + * RS232's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiRS232(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_PER_CHAR_mQ = mote.getType().getConfig() + .getDoubleValue(ContikiRS232.class, "CONSUMPTION_PER_CHAR_mQ"); + RAISES_EXTERNAL_INTERRUPT = mote.getType().getConfig() + .getBooleanValue(ContikiRS232.class, "EXTERNAL_INTERRUPT_bool"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"rs232_interface"}; + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + if (moteMem.getByteValueOf("simSerialSendingFlag") == 1) { + int totalLength = moteMem.getIntValueOf("simSerialSendingLength"); + byte[] bytes = moteMem.getByteArray("simSerialSendingData", totalLength); + char[] chars = new char[bytes.length]; + for (int i = 0; i < chars.length; i++) + chars[i] = (char) bytes[i]; + + myEnergyConsumption = ENERGY_CONSUMPTION_PER_CHAR_mQ * totalLength; + + String message = String.valueOf(chars); + lastSerialMessage = message; + + moteMem.setByteValueOf("simSerialSendingFlag", (byte) 0); + moteMem.setIntValueOf("simSerialSendingLength", (int) 0); + + this.setChanged(); + this.notifyObservers(mote); + } else + myEnergyConsumption = 0.0; + } + + /** + * Returns all serial messages sent by mote the last tick that anything was + * sent. + * + * @return Last serial messages sent by mote. + */ + public String getSerialMessages() { + return lastSerialMessage; + } + + /** + * Send a serial message to mote. + * + * @param message + * Message that mote should receive + */ + public void sendSerialMessage(String message) { + + if (logTextPane != null) { + logTextPane.append("> " + message + "\n"); + } + + // Flag for incoming data + moteMem.setByteValueOf("simSerialReceivingFlag", (byte) 1); + + byte[] dataToAppend = message.getBytes(); + + // Increase receiving size + int oldSize = moteMem.getIntValueOf("simSerialReceivingLength"); + moteMem.setIntValueOf("simSerialReceivingLength", oldSize + + dataToAppend.length); + int newSize = moteMem.getIntValueOf("simSerialReceivingLength"); + + // Write buffer characters + byte[] oldData = moteMem.getByteArray("simSerialReceivingData", oldSize); + byte[] newData = new byte[newSize]; + + for (int i = 0; i < oldData.length; i++) + newData[i] = oldData[i]; + + for (int i = 0; i < message.length(); i++) + newData[i + oldSize] = dataToAppend[i]; + + moteMem.setByteArray("simSerialReceivingData", newData); + + if (RAISES_EXTERNAL_INTERRUPT) + mote.setState(Mote.STATE_ACTIVE); + + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + + if (logTextPane == null) + logTextPane = new JTextArea(); + + // Send RS232 data visualizer + JPanel sendPane = new JPanel(); + final JTextField sendTextField = new JTextField(15); + JButton sendButton = new JButton("Send data"); + sendButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sendSerialMessage(sendTextField.getText()); + } + }); + sendPane.add(BorderLayout.WEST, sendTextField); + sendPane.add(BorderLayout.EAST, sendButton); + + // Receive RS232 data visualizer + logTextPane.setOpaque(false); + logTextPane.setEditable(false); + + if (lastSerialMessage == null) + logTextPane.setText(""); + else + logTextPane.append(lastSerialMessage); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + logTextPane.append("< " + lastSerialMessage + "\n"); + logTextPane.setCaretPosition(logTextPane.getDocument().getLength()); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + JScrollPane scrollPane = new JScrollPane(logTextPane); + scrollPane.setPreferredSize(new Dimension(100, 100)); + panel.add(BorderLayout.NORTH, new JLabel("Last serial data:")); + panel.add(BorderLayout.CENTER, scrollPane); + panel.add(BorderLayout.SOUTH, sendPane); + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRadio.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRadio.java new file mode 100644 index 000000000..89d4663d4 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiRadio.java @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiRadio.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; +import se.sics.cooja.interfaces.Radio; + +/** + * This class represents a radio transciever. + * + * It needs read/write access to the following core variables: + *

    + *
  • char simSentPacket (1=mote has new outgoing data, else no new outgoing + * data) + *
  • char simReceivedPacket (1=mote has new incoming data, else no new + * incoming data) + *
  • char simEtherBusy (1=ether is busy, MAC may try to resend later, else + * not busy) + *
  • int simReceivedPacketSize (size of new received data packet) + *
  • int simSentPacketSize (size of new sent data packet) + *
  • byte[] simSentPacketData (data of new sent data packet) + *
  • byte[] simReceivedPacketData (data of new received data packet) + *
  • char simRadioHWOn (radio hardware status (on/off)) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • radio_interface + *
+ *

+ * This observable is changed and notifies observers whenever either the send + * status or listen status is changed. If current listen status is HEARS_PACKET + * just before a mote tick, the current packet data is transferred to the core. + * Otherwise no data will be transferred. If core has sent a packet, current + * sent status will be set to SENT_SOMETHING when returning from the mote tick + * that sent the packet. This status will be reset to SENT_NOTHING just before + * next tick. + * + * @author Fredrik Osterlind + */ +public class ContikiRadio extends Radio implements ContikiMoteInterface { + private Mote myMote; + private SectionMoteMemory myMoteMemory; + private static Logger logger = Logger.getLogger(ContikiRadio.class); + + /** + * Approximate energy consumption of an active radio. ESB measured energy + * consumption is 5 mA. TODO Measure energy consumption + */ + public final double ENERGY_CONSUMPTION_RADIO_mA; + + private final boolean RAISES_EXTERNAL_INTERRUPT; + + private double energyActiveRadioPerTick = -1; + + private int mySendState = SENT_NOTHING; + private int myListenState = HEARS_NOTHING; + + private byte[] packetToMote = null; + private byte[] packetFromMote = null; + + private boolean radioOn = true; + + private double myEnergyConsumption=0.0; + + /** + * Creates an interface to the radio at mote. + * + * @param mote + * Radio's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiRadio(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_RADIO_mA = mote.getType().getConfig().getDoubleValue(ContikiRadio.class, "ACTIVE_CONSUMPTION_mA"); + RAISES_EXTERNAL_INTERRUPT = mote.getType().getConfig().getBooleanValue(ContikiRadio.class, "EXTERNAL_INTERRUPT_bool"); + + this.myMote = mote; + this.myMoteMemory = (SectionMoteMemory) mote.getMemory(); + + if (energyActiveRadioPerTick < 0) + energyActiveRadioPerTick = ENERGY_CONSUMPTION_RADIO_mA * mote.getSimulation().getTickTimeInSeconds(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[] { "radio_interface" }; + } + + public byte[] getLastPacketSent() { + return packetFromMote; + } + + public byte[] getLastPacketReceived() { + return packetToMote; + } + + public void receivePacket(byte[] data) { + packetToMote = data; + } + + public int getSendState() { + return mySendState; + } + + public int getListenState() { + return myListenState; + } + + public void setListenState(int newStatus) { + if (newStatus != myListenState) { + myListenState = newStatus; + this.setChanged(); + this.notifyObservers(myMote.getInterfaces().getPosition()); + } + + // If mote is inactive, wake it up + if (RAISES_EXTERNAL_INTERRUPT) + myMote.setState(Mote.STATE_ACTIVE); + } + + public void advanceListenState() { + if (myListenState == HEARS_NOTHING) { + setListenState(HEARS_PACKET); + } else + setListenState(HEARS_NOISE); + } + + public void doActionsBeforeTick() { + // If radio hardware is turned off, we don't need to do anything.. + if (!radioOn) { + myEnergyConsumption = 0.0; + return; + } + myEnergyConsumption = energyActiveRadioPerTick; + + // Set ether status + if (getListenState() == HEARS_PACKET || + getListenState() == HEARS_NOISE || + getSendState() == SENT_SOMETHING) { + myMoteMemory.setByteValueOf("simEtherBusy", (byte) 1); + } else { + myMoteMemory.setByteValueOf("simEtherBusy", (byte) 0); + } + + if (getListenState() == HEARS_NOTHING) { + // Haven't heard anything, nothing to do + } else if (getListenState() == HEARS_PACKET) { + // Heard only one packet, transfer to mote ok + myMoteMemory.setByteValueOf("simReceivedPacket", (byte) 1); + myMoteMemory.setIntValueOf("simReceivedPacketSize", packetToMote.length); + myMoteMemory.setByteArray("simReceivedPacketData", packetToMote); + } else if (getListenState() == HEARS_NOISE) { + // Heard several packets or noise, transfer failed + } + + // Reset send flag + setSendStatus(SENT_NOTHING); + } + + public void doActionsAfterTick() { + // Check new radio hardware status + if (myMoteMemory.getByteValueOf("simRadioHWOn") == 1) { + radioOn = true; + } else { + radioOn = false; + return; + } + + // Reset listen flag + setListenState(HEARS_NOTHING); + + if (fetchPacketFromCore()) { + setSendStatus(SENT_SOMETHING); + } + } + + private void setSendStatus(int newStatus) { + if (newStatus != mySendState) { + mySendState = newStatus; + this.setChanged(); + this.notifyObservers(myMote.getInterfaces().getPosition()); + } + } + + private boolean fetchPacketFromCore() { + if (myMoteMemory.getByteValueOf("simSentPacket") == 1) { + // TODO Increase energy consumption, we are sending a packet... + + myMoteMemory.setByteValueOf("simSentPacket", (byte) 0); + + int size = myMoteMemory.getIntValueOf("simSentPacketSize"); + + packetFromMote = myMoteMemory.getByteArray("simSentPacketData", size); + + myMoteMemory.setIntValueOf("simSentPacketSize", 0); + + return true; + } + return false; + } + + public JPanel getInterfaceVisualizer() { + // Location + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + final JLabel listenLabel = new JLabel(); + final JLabel sendLabel = new JLabel(); + + if (getListenState() == HEARS_NOISE) + listenLabel.setText("Current listen status: hears noise"); + else if (getListenState() == HEARS_NOTHING) + listenLabel.setText("Current listen status: hears nothing"); + else if (getListenState() == HEARS_PACKET) + listenLabel.setText("Current listen status: hears a packet"); + + if (getSendState() == SENT_NOTHING) + sendLabel.setText("Current sending status: sent nothing"); + else if (getSendState() == SENT_SOMETHING) + sendLabel.setText("Current sending status: sent a packet"); + + panel.add(listenLabel); + panel.add(sendLabel); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + if (getListenState() == HEARS_NOISE) + listenLabel.setText("Current listen status: hears noise"); + else if (getListenState() == HEARS_NOTHING) + listenLabel.setText("Current listen status: hears nothing"); + else if (getListenState() == HEARS_PACKET) + listenLabel.setText("Current listen status: hears a packet"); + + if (getSendState() == SENT_NOTHING) + sendLabel.setText("Current sending status: sent nothing"); + else if (getSendState() == SENT_SOMETHING) + sendLabel.setText("Current sending status: sent a packet"); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiVib.java b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiVib.java new file mode 100644 index 000000000..e826af0b6 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/contikimote/interfaces/ContikiVib.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ContikiVib.java,v 1.1 2006/08/21 12:13:05 fros4943 Exp $ + */ + +package se.sics.cooja.contikimote.interfaces; + +import java.awt.event.*; +import java.util.Collection; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.contikimote.ContikiMoteInterface; + +/** + * This class represents a vibration sensor. + * + * It needs read/write access to the following core variables: + *

    + *
  • char simVibChanged (1=changed, else not changed) + *
  • char simVibIsActive (1=active, else inactive) + *
+ *

+ * Dependency core interfaces are: + *

    + *
  • vib_interface + *
+ *

+ * This observable never notifies observers. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Vibration sensor") +public class ContikiVib extends MoteInterface implements ContikiMoteInterface { + private static Logger logger = Logger.getLogger(ContikiVib.class); + + /** + * Approximate energy consumption of an active vibrator sensor. ESB measured + * energy consumption is 1.58 mA. + */ + public final double ENERGY_CONSUMPTION_VIB_mA; + + private final boolean RAISES_EXTERNAL_INTERRUPT; + + private double energyActiveVibPerTick = -1; + + private Mote mote; + private SectionMoteMemory moteMem; + private double myEnergyConsumption = 0.0; + + /** + * Creates an interface to the vibration sensor at mote. + * + * @param mote + * Vib's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public ContikiVib(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_VIB_mA = mote.getType().getConfig().getDoubleValue( + ContikiVib.class, "ACTIVE_CONSUMPTION_mA"); + RAISES_EXTERNAL_INTERRUPT = mote.getType().getConfig() + .getBooleanValue(ContikiVib.class, "EXTERNAL_INTERRUPT_bool"); + + this.mote = mote; + this.moteMem = (SectionMoteMemory) mote.getMemory(); + + if (energyActiveVibPerTick < 0) + energyActiveVibPerTick = ENERGY_CONSUMPTION_VIB_mA + * mote.getSimulation().getTickTimeInSeconds(); + } + + public static String[] getCoreInterfaceDependencies() { + return new String[]{"vib_interface"}; + } + + /** + * Simulates a change in the vibration sensor. + */ + public void triggerChange() { + if (moteMem.getByteValueOf("simVibIsActive") == 1) { + moteMem.setByteValueOf("simVibChanged", (byte) 1); + + // If mote is inactive, wake it up + if (RAISES_EXTERNAL_INTERRUPT) + mote.setState(Mote.STATE_ACTIVE); + } + } + + public void doActionsBeforeTick() { + if (moteMem.getByteValueOf("simVibIsActive") == 1) { + myEnergyConsumption = energyActiveVibPerTick; // ESB measured value + } else + myEnergyConsumption = 0.0; + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + JPanel panel = new JPanel(); + final JButton clickButton = new JButton("Vibrate!"); + + panel.add(clickButton); + + clickButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + triggerChange(); + } + }); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + } + + public double energyConsumptionPerTick() { + return myEnergyConsumption; + } + + public Collection getConfigXML() { + return null; + } + + public void setConfigXML(Collection configXML) { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib1.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib1.java new file mode 100644 index 000000000..53e1cad79 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib1.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib1.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib1 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib1(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib2.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib2.java new file mode 100644 index 000000000..adee132f5 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib2.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib2.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib2 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib2(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib3.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib3.java new file mode 100644 index 000000000..71b74342f --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib3.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib3.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib3 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib3(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib4.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib4.java new file mode 100644 index 000000000..2e45617b3 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib4.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib4.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib4 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib4(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib5.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib5.java new file mode 100644 index 000000000..7b10f0cf2 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib5.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib5.java,v 1.1 2006/08/21 12:12:57 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib5 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib5(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib6.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib6.java new file mode 100644 index 000000000..db0d160d3 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib6.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib6.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib6 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib6(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib7.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib7.java new file mode 100644 index 000000000..6e2815708 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib7.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib7.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib7 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib7(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/corecomm/Lib8.java b/tools/cooja/java/se/sics/cooja/corecomm/Lib8.java new file mode 100644 index 000000000..ceb3abf63 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/corecomm/Lib8.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Lib8.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.corecomm; +import java.io.File; + +import se.sics.cooja.*; + +/** + * @see CoreComm + * @author Fredrik Osterlind + */ +public class Lib8 extends CoreComm { + + /** + * Loads library libFile. + * + * @see CoreComm + * @param libFile Library file + */ + public Lib8(File libFile) { + System.load(libFile.getAbsolutePath()); + init(); + } + + public native void tick(); + public native void init(); + public native int getReferenceAbsAddr(); + public native byte[] getMemory(int start, int length); + public native void setMemory(int start, int length, byte[] mem); + +} diff --git a/tools/cooja/java/se/sics/cooja/dialogs/AddMoteDialog.java b/tools/cooja/java/se/sics/cooja/dialogs/AddMoteDialog.java new file mode 100644 index 000000000..01328fb85 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/AddMoteDialog.java @@ -0,0 +1,502 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: AddMoteDialog.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +package se.sics.cooja.dialogs; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import java.text.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; + +/** + * A dialog for adding motes. + * + * @author Fredrik Osterlind + */ +public class AddMoteDialog extends JDialog { + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AddMoteDialog.class); + + private AddMotesEventHandler myEventHandler = new AddMotesEventHandler(); + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private Vector newMotes = null; + + private boolean settingsOK = true; + private JButton addButton; + + private MoteType moteType = null; + + private JFormattedTextField numberOfMotesField, startX, endX, startY, endY, + startZ, endZ; + private JComboBox positionDistributionBox, ipDistributionBox; + + /** + * Shows a dialog which enables a user to create and add motes of the given + * type. + * + * @param parentFrame + * Parent frame for dialog + * @param moteType + * Mote type + * @return New motes or null if aborted + */ + public static Vector showDialog(Frame parentFrame, MoteType moteType) { + + AddMoteDialog myDialog = new AddMoteDialog(parentFrame, moteType); + myDialog.setLocationRelativeTo(parentFrame); + myDialog.checkSettings(); + + if (myDialog != null) { + myDialog.setVisible(true); + } + return myDialog.newMotes; + } + + private AddMoteDialog(Frame frame, MoteType moteType) { + super(frame, "Add motes (" + moteType.getDescription() + ")", true); + this.moteType = moteType; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + JFormattedTextField numberField; + JButton button; + JComboBox comboBox; + NumberFormat integerFormat = NumberFormat.getIntegerInstance(); + NumberFormat doubleFormat = NumberFormat.getNumberInstance(); + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Cancel"); + button.setActionCommand("cancel"); + button.addActionListener(myEventHandler); + buttonPane.add(button); + + button = new JButton("Create and Add"); + button.setEnabled(settingsOK); + button.setActionCommand("add"); + button.addActionListener(myEventHandler); + this.getRootPane().setDefaultButton(button); + addButton = button; + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + // MAIN PART + + // Number of new motes + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Number of new motes"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + numberField = new JFormattedTextField(integerFormat); + numberField.setValue(new Integer(1)); + numberField.setColumns(10); + numberField.addPropertyChangeListener("value", myEventHandler); + numberOfMotesField = numberField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(numberField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // IP address distribution + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("IP Addressing"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + Vector> ipDistributors = GUI.currentGUI + .getRegisteredIPDistributors(); + String[] ipDistributions = new String[ipDistributors.size()]; + for (int i = 0; i < ipDistributions.length; i++) + ipDistributions[i] = GUI.getDescriptionOf(ipDistributors.get(i)); + + comboBox = new JComboBox(ipDistributions); + + comboBox.setSelectedIndex(0); + comboBox.addActionListener(myEventHandler); + comboBox.addFocusListener(myEventHandler); + ipDistributionBox = comboBox; + label.setLabelFor(comboBox); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(comboBox); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Position distribution + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Positioning"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + Vector> positioners = GUI.currentGUI + .getRegisteredPositioners(); + String[] posDistributions = new String[positioners.size()]; + for (int i = 0; i < posDistributions.length; i++) + posDistributions[i] = GUI.getDescriptionOf(positioners.get(i)); + + comboBox = new JComboBox(posDistributions); + + comboBox.setSelectedIndex(0); + comboBox.addActionListener(myEventHandler); + comboBox.addFocusListener(myEventHandler); + positionDistributionBox = comboBox; + label.setLabelFor(comboBox); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(comboBox); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Position interval X + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + + label = new JLabel("Position interval"); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("X "); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(0.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + startX = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("<->"); + label.setPreferredSize(new Dimension(LABEL_WIDTH / 4, LABEL_HEIGHT)); + smallPane.add(label); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(100.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + endX = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Position interval Y + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + + label = new JLabel(""); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("Y "); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(0.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + startY = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("<->"); + label.setPreferredSize(new Dimension(LABEL_WIDTH / 4, LABEL_HEIGHT)); + smallPane.add(label); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(100.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + endY = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + // Position interval Z + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + + label = new JLabel(""); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("Z "); + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(0.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + startZ = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + label = new JLabel("<->"); + label.setPreferredSize(new Dimension(LABEL_WIDTH / 4, LABEL_HEIGHT)); + smallPane.add(label); + + numberField = new JFormattedTextField(doubleFormat); + numberField.setValue(new Double(0.0)); + numberField.setColumns(4); + numberField.addPropertyChangeListener("value", myEventHandler); + endZ = numberField; + smallPane.add(numberField); + smallPane.add(Box.createHorizontalStrut(10)); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + Container contentPane = getContentPane(); + contentPane.add(mainPane, BorderLayout.NORTH); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + pack(); + } + + private void checkSettings() { + // Check settings + settingsOK = true; + + // Check X interval + if (((Number) startX.getValue()).doubleValue() > ((Number) endX.getValue()) + .doubleValue()) { + startX.setBackground(Color.RED); + startX.setToolTipText("Malformed interval"); + endX.setBackground(Color.RED); + endX.setToolTipText("Malformed interval"); + settingsOK = false; + } else { + startX.setBackground(Color.WHITE); + startX.setToolTipText(null); + endX.setBackground(Color.WHITE); + endX.setToolTipText(null); + } + + // Check Y interval + if (((Number) startY.getValue()).doubleValue() > ((Number) endY.getValue()) + .doubleValue()) { + startY.setBackground(Color.RED); + startY.setToolTipText("Malformed interval"); + endY.setBackground(Color.RED); + endY.setToolTipText("Malformed interval"); + settingsOK = false; + } else { + startY.setBackground(Color.WHITE); + startY.setToolTipText(null); + endY.setBackground(Color.WHITE); + endY.setToolTipText(null); + } + + // Check Z interval + if (((Number) startZ.getValue()).doubleValue() > ((Number) endZ.getValue()) + .doubleValue()) { + startZ.setBackground(Color.RED); + startZ.setToolTipText("Malformed interval"); + endZ.setBackground(Color.RED); + endZ.setToolTipText("Malformed interval"); + settingsOK = false; + } else { + startZ.setBackground(Color.WHITE); + startZ.setToolTipText(null); + endZ.setBackground(Color.WHITE); + endZ.setToolTipText(null); + } + + // Check number of new motes + if (((Number) numberOfMotesField.getValue()).intValue() < 1) { + numberOfMotesField.setBackground(Color.RED); + numberOfMotesField.setToolTipText("Must be >= 1"); + settingsOK = false; + } else { + numberOfMotesField.setBackground(Color.WHITE); + numberOfMotesField.setToolTipText(null); + } + + addButton.setEnabled(settingsOK); + } + + private class AddMotesEventHandler + implements + ActionListener, + FocusListener, + PropertyChangeListener { + public void propertyChange(PropertyChangeEvent e) { + checkSettings(); + } + public void focusGained(FocusEvent e) { + // NOP + } + public void focusLost(FocusEvent e) { + checkSettings(); + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("cancel")) { + newMotes = null; + dispose(); + } else if (e.getActionCommand().equals("add")) { + newMotes = new Vector(); + + // Create new motes + int motesToAdd = ((Number) numberOfMotesField.getValue()).intValue(); + while (newMotes.size() < motesToAdd) { + Mote newMote = moteType.generateMote(GUI.currentSimulation); + newMotes.add(newMote); + } + + // Position new motes + Class positionerClass = null; + for (Class positioner : GUI.currentGUI + .getRegisteredPositioners()) { + if (GUI.getDescriptionOf(positioner).equals( + (String) positionDistributionBox.getSelectedItem())) + positionerClass = positioner; + } + + Positioner positioner = Positioner.generateInterface(positionerClass, + ((Number) numberOfMotesField.getValue()).intValue(), + ((Number) startX.getValue()).doubleValue(), ((Number) endX + .getValue()).doubleValue(), ((Number) startY.getValue()) + .doubleValue(), ((Number) endY.getValue()).doubleValue(), + ((Number) startZ.getValue()).doubleValue(), ((Number) endZ + .getValue()).doubleValue()); + + if (positioner == null) { + logger.fatal("Could not create positioner"); + dispose(); + return; + } + + for (int i = 0; i < newMotes.size(); i++) { + Position newPosition = newMotes.get(i).getInterfaces().getPosition(); + if (newPosition != null) { + double[] newPositionArray = positioner.getNextPosition(); + if (newPositionArray.length >= 3) + newPosition.setCoordinates(newPositionArray[0], + newPositionArray[1], newPositionArray[2]); + else if (newPositionArray.length >= 2) + newPosition.setCoordinates(newPositionArray[0], + newPositionArray[1], 0); + else if (newPositionArray.length >= 1) + newPosition.setCoordinates(newPositionArray[0], 0, 0); + else + newPosition.setCoordinates(0, 0, 0); + } + } + + // Set unique mote id's for all new motes + int nextMoteID = 1; + for (int i = 0; i < GUI.currentSimulation.getMotesCount(); i++) { + MoteID moteID = GUI.currentSimulation.getMote(i).getInterfaces() + .getMoteID(); + if (moteID != null && moteID.getMoteID() >= nextMoteID) + nextMoteID = moteID.getMoteID() + 1; + } + + for (int i = 0; i < newMotes.size(); i++) { + MoteID moteID = newMotes.get(i).getInterfaces().getMoteID(); + if (moteID != null) { + moteID.setMoteID(nextMoteID++); + } + } + + // IP address new motes + Class ipDistClass = null; + for (Class ipDistributor : GUI.currentGUI + .getRegisteredIPDistributors()) { + if (GUI.getDescriptionOf(ipDistributor).equals( + (String) ipDistributionBox.getSelectedItem())) + ipDistClass = ipDistributor; + } + + IPDistributor ipDistributor = IPDistributor.generateInterface( + ipDistClass, newMotes); + + if (ipDistributor == null) { + logger.fatal("Could not create IP distributor"); + dispose(); + return; + } + + for (int i = 0; i < newMotes.size(); i++) { + String newIPString = ipDistributor.getNextIPAddress(); + if (newMotes.get(i).getInterfaces().getIPAddress() != null) + newMotes.get(i).getInterfaces().getIPAddress().setIPString( + newIPString); + } + + dispose(); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/dialogs/CreateSimDialog.java b/tools/cooja/java/se/sics/cooja/dialogs/CreateSimDialog.java new file mode 100644 index 000000000..f593e4f65 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/CreateSimDialog.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: CreateSimDialog.java,v 1.1 2006/08/21 12:13:02 fros4943 Exp $ + */ + +package se.sics.cooja.dialogs; + +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.text.*; +import java.util.Vector; + +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * A dialog for creating and configuring a simulation. + * + * @author Fredrik Osterlind + */ +public class CreateSimDialog extends JDialog { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(CreateSimDialog.class); + + private AddSimEventHandler myEventHandler = new AddSimEventHandler(); + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private Simulation mySimulation = null; + + private CreateSimDialog myDialog; + + private JFormattedTextField delayTime, simulationTime, tickTime; + private JTextField title; + private JComboBox radioMediumBox; + + private JTextField logFilename; + private JCheckBox logCheckBox; + + /** + * Shows a dialog for configuring a simulation. + * + * @param parentFrame Parent frame for dialog + * @param simulationToConfigure Simulation to configure + * @return True if simulation configured correctly + */ + public static boolean showDialog(Frame parentFrame, Simulation simulationToConfigure) { + CreateSimDialog myDialog = new CreateSimDialog(parentFrame); + + myDialog.mySimulation = simulationToConfigure; + + // Set title + if (simulationToConfigure.getTitle() != null) { + // Title already preset + myDialog.title.setText(simulationToConfigure.getTitle()); + } else { + // Suggest title + myDialog.title.setText("[enter simulation title]"); + } + + // Set delay time + myDialog.delayTime.setValue(new Integer(simulationToConfigure.getDelayTime())); + + // Set simulation time + myDialog.simulationTime.setValue(new Integer(simulationToConfigure.getSimulationTime())); + + // Set tick time + myDialog.tickTime.setValue(new Integer(simulationToConfigure.getTickTime())); + + // Select radio medium + if (simulationToConfigure.getRadioMedium() != null) { + Class radioMediumClass = + simulationToConfigure.getRadioMedium().getClass(); + + String currentDescription = GUI.getDescriptionOf(radioMediumClass); + + for (int i=0; i < myDialog.radioMediumBox.getItemCount(); i++) { + String menuDescription = (String) myDialog.radioMediumBox.getItemAt(i); + if (menuDescription.equals(currentDescription)) { + myDialog.radioMediumBox.setSelectedIndex(i); + break; + } + } + } + + // Set position and focus of dialog + myDialog.setLocationRelativeTo(parentFrame); + myDialog.title.requestFocus(); + myDialog.title.select(0, myDialog.title.getText().length()); + + myDialog.setVisible(true); + + if (myDialog.mySimulation != null) { + // Simulation configured correctly + return true; + } + return false; + } + + private CreateSimDialog(Frame frame) { + super(frame, "Create new simulation", true); + + myDialog = this; + + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + + JLabel label; + JTextField textField; + JPanel smallPane; + JButton button; + JComboBox comboBox; + JFormattedTextField numberField; + NumberFormat integerFormat = NumberFormat.getIntegerInstance(); + + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Cancel"); + button.setActionCommand("cancel"); + button.addActionListener(myEventHandler); + buttonPane.add(button); + + button = new JButton("Create"); + button.setActionCommand("create"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + myDialog.rootPane.setDefaultButton(button); + buttonPane.add(button); + + + // MAIN PART + + // Title + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Simulation title"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText("[no title]"); + textField.setColumns(25); + title = textField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(textField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + // Radio Medium selection + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Radio Medium"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + Vector radioMediumDescriptions = new Vector(); + for (Class radioMediumClass: GUI.currentGUI.getRegisteredRadioMediums()) { + String description = GUI.getDescriptionOf(radioMediumClass); + radioMediumDescriptions.add(description); + } + + comboBox = new JComboBox(radioMediumDescriptions); + + comboBox.setSelectedIndex(0); + radioMediumBox = comboBox; + label.setLabelFor(comboBox); + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(comboBox); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + // Radio Medium Logging selection + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + logCheckBox = new JCheckBox("Log all radio traffic?"); + logCheckBox.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + textField = new JTextField(); + textField.setText("[filename]"); + textField.setColumns(25); + logFilename = textField; + + smallPane.add(logCheckBox); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(textField); + + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + // Delay time + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Delay time (ms)"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + numberField = new JFormattedTextField(integerFormat); + numberField.setValue(new Integer(100)); + numberField.setColumns(4); + delayTime = numberField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(150)); + smallPane.add(numberField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + // Simulation start time + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Simulation start time (ms)"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + numberField = new JFormattedTextField(integerFormat); + numberField.setValue(new Integer(0)); + numberField.setColumns(4); + simulationTime = numberField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(150)); + smallPane.add(numberField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + // Tick time + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Tick time (ms)"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + + numberField = new JFormattedTextField(integerFormat); + numberField.setValue(new Integer(1)); + numberField.setColumns(4); + tickTime = numberField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(150)); + smallPane.add(numberField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + mainPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); + + Container contentPane = getContentPane(); + contentPane.add(mainPane, BorderLayout.NORTH); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + pack(); + } + + private class AddSimEventHandler implements ActionListener { + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("cancel")) { + mySimulation = null; + dispose(); + } else if (e.getActionCommand().equals("create")) { + mySimulation.setDelayTime(((Number) delayTime.getValue()).intValue()); + mySimulation.setSimulationTime(((Number) simulationTime.getValue()).intValue()); + mySimulation.setTickTime(((Number) tickTime.getValue()).intValue()); + mySimulation.setTitle(title.getText()); + + String currentRadioMediumDescription = (String) radioMediumBox.getSelectedItem(); + for (Class radioMediumClass: GUI.currentGUI.getRegisteredRadioMediums()) { + String radioMediumDescription = GUI.getDescriptionOf(radioMediumClass); + + if (currentRadioMediumDescription.equals(radioMediumDescription)) { + try { + mySimulation.setRadioMedium(radioMediumClass.newInstance()); + } catch (Exception ex) { + logger.fatal("Exception when creating radio medium: " + ex); + mySimulation.setRadioMedium(null); + } + break; + } + } + + if (logCheckBox.isSelected()) { + ConnectionLogger connLogger = new ConnectionLogger(new File(logFilename.getText())); + mySimulation.getRadioMedium().setConnectionLogger(connLogger); + } + + dispose(); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/dialogs/ExternalToolsDialog.java b/tools/cooja/java/se/sics/cooja/dialogs/ExternalToolsDialog.java new file mode 100644 index 000000000..2d3d6b379 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/ExternalToolsDialog.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: ExternalToolsDialog.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + */ + +package se.sics.cooja.dialogs; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * A dialog for viewing/editing external tools settings. + * Allows user to change paths and arguments to compilers, linkers etc. + * + * @author Fredrik Osterlind + */ +public class ExternalToolsDialog extends JDialog { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(ExternalToolsDialog.class); + + private ExternalToolsEventHandler myEventHandler = new ExternalToolsEventHandler(); + + private final static int LABEL_WIDTH = 220; + private final static int LABEL_HEIGHT = 15; + + private ExternalToolsDialog myDialog; + + private JTextField textFields[]; + + /** + * Creates a dialog for viewing/editing external tools settings. + * + * @param parentFrame + * Parent frame for dialog + */ + public static void showDialog(Frame parentFrame) { + ExternalToolsDialog myDialog = new ExternalToolsDialog(parentFrame); + myDialog.setLocationRelativeTo(parentFrame); + + if (myDialog != null) { + myDialog.setVisible(true); + } + } + + private ExternalToolsDialog(Frame frame) { + super(frame, "Edit Settings", true); + + myDialog = this; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + JButton button; + JTextField textField; + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Cancel"); + button.setActionCommand("cancel"); + button.addActionListener(myEventHandler); + buttonPane.add(button); + + button = new JButton("Reset"); + button.setActionCommand("reset"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + button = new JButton("OK (Saves)"); + button.setActionCommand("ok"); + button.addActionListener(myEventHandler); + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + buttonPane.add(button); + + // MAIN PART + textFields = new JTextField[GUI.getExternalToolsSettingsCount()]; + for (int i = 0; i < GUI.getExternalToolsSettingsCount(); i++) { + // Add text fields for every changable property + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel(GUI.getExternalToolsSettingName(i)); + label.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); + + textField = new JTextField(35); + textField.setText(""); + textField.addFocusListener(myEventHandler); + textFields[i] = textField; + + smallPane.add(label); + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(textField); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0, 5))); + } + + // Set actual used values into all text fields + updateTextFields(); + + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + Container contentPane = getContentPane(); + contentPane.add(mainPane, BorderLayout.NORTH); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + pack(); + } + + private void updateTextFields() { + for (int i = 0; i < GUI.getExternalToolsSettingsCount(); i++) { + textFields[i].setText(GUI.getExternalToolsSetting(GUI.getExternalToolsSettingName(i), "")); + } + } + + private class ExternalToolsEventHandler + implements + ActionListener, + FocusListener { + public void focusGained(FocusEvent e) { + // NOP + } + public void focusLost(FocusEvent e) { + // NOP + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("reset")) { + GUI.loadExternalToolsDefaultSettings(); + updateTextFields(); + } else if (e.getActionCommand().equals("ok")) { + for (int i = 0; i < GUI.getExternalToolsSettingsCount(); i++) { + GUI.setExternalToolsSetting(GUI.getExternalToolsSettingName(i), textFields[i].getText() + .trim()); + } + GUI.saveExternalToolsUserSettings(); + myDialog.dispose(); + } else if (e.getActionCommand().equals("cancel")) { + myDialog.dispose(); + } else + logger.debug("Unhandled command: " + e.getActionCommand()); + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/dialogs/MessageList.java b/tools/cooja/java/se/sics/cooja/dialogs/MessageList.java new file mode 100644 index 000000000..ec3699c08 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/MessageList.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MessageList.java,v 1.1 2006/08/21 12:13:01 fros4943 Exp $ + * + * ----------------------------------------------------------------- + * + * Author : Adam Dunkels, Joakim Eriksson, Niclas Finne, Fredrik Osterlind + * Created : 2006-06-14 + * Updated : $Date: 2006/08/21 12:13:01 $ + * $Revision: 1.1 $ + */ +package se.sics.cooja.dialogs; +import java.awt.Color; +import java.awt.Component; +import javax.swing.DefaultListCellRenderer; +import javax.swing.DefaultListModel; +import javax.swing.JList; +import javax.swing.ListModel; +import javax.swing.ListSelectionModel; + +public class MessageList extends JList { + + public static final int NORMAL = 0; + public static final int WARNING = 1; + public static final int ERROR = 2; + + private Color[] foregrounds = new Color[] { null, Color.red }; + private Color[] backgrounds = new Color[] { null, null }; + + public MessageList() { + super.setModel(new DefaultListModel()); + setCellRenderer(new MessageRenderer()); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + public Color getForeground(int type) { + Color c = type > 0 && type <= foregrounds.length + ? foregrounds[type - 1] : null; + return c == null ? getForeground() : c; + } + + public void setForeground(int type, Color color) { + if (type > 0 && type <= foregrounds.length) { + foregrounds[type - 1] = color; + } else if (type == NORMAL) { + setForeground(color); + } + } + + public Color getBackground(int type) { + Color c = type > 0 && type <= backgrounds.length + ? backgrounds[type - 1] : null; + return c == null ? getBackground() : c; + } + + public void setBackground(int type, Color color) { + if (type > 0 && type <= backgrounds.length) { + backgrounds[type - 1] = color; + } else if (type == NORMAL) { + setBackground(color); + } + } + + public void addMessage(String message) { + addMessage(message, NORMAL); + } + + public void addMessage(String message, int type) { + MessageContainer msg = new MessageContainer(message, type); + ((DefaultListModel) getModel()).addElement(msg); + ensureIndexIsVisible(getModel().getSize() - 1); + } + + public void clearMessages() { + ((DefaultListModel) getModel()).clear(); + } + + public void setModel(ListModel model) { + throw new IllegalArgumentException("changing model not permitted"); + } + + + // ------------------------------------------------------------------- + // MessageContainer + // ------------------------------------------------------------------- + + private static class MessageContainer { + public final int type; + public final String message; + + public MessageContainer(String message, int type) { + this.message = message; + this.type = type; + } + + public String toString() { + return message; + } + + } // end of inner class MessageContainer + + + // ------------------------------------------------------------------- + // Renderer for messages + // ------------------------------------------------------------------- + + private static class MessageRenderer extends DefaultListCellRenderer { + + public Component getListCellRendererComponent( + JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) + { + super.getListCellRendererComponent(list, value, index, isSelected, + cellHasFocus); + MessageContainer msg = (MessageContainer) value; + setForeground(((MessageList) list).getForeground(msg.type)); + setBackground(((MessageList) list).getBackground(msg.type)); + return this; + } + + } // end of inner class MessageRenderer + +} // end of MessagList diff --git a/tools/cooja/java/se/sics/cooja/dialogs/UserPlatformsDialog.java b/tools/cooja/java/se/sics/cooja/dialogs/UserPlatformsDialog.java new file mode 100644 index 000000000..97504fb57 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/dialogs/UserPlatformsDialog.java @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: UserPlatformsDialog.java,v 1.1 2006/08/21 12:13:02 fros4943 Exp $ + */ + +package se.sics.cooja.dialogs; + +import java.awt.*; +import java.awt.event.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import javax.swing.*; + +import org.apache.log4j.Logger; + +import se.sics.cooja.GUI; +import se.sics.cooja.PlatformConfig; + +/** + * This dialog allows a user to manage the user platforms configuration. User + * platforms can be added, removed or reordered. The resulting platform + * configuration can also be viewed. + * + * This dialog reads from the external platform configuration files in each user + * platform, as well as from any specified default configuration files. + * + * @author Fredrik Osterlind + */ +public class UserPlatformsDialog extends JDialog { + + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(UserPlatformsDialog.class); + + private List changablePlatformsList = new List(); + private List fixedPlatformsList = null; + private Vector fixedUserPlatforms = null; + private Vector changableUserPlatforms = null; + + private UserPlatformsDialog myDialog; + private Frame myParentFrame; + + /** + * Allows user to alter the given user platforms list by adding new, + * reordering or removing user platforms. Only the changable user platforms + * may be changed, + * + * @param parentFrame + * Parent frame + * @param changablePlatforms + * Changeable user platforms + * @param fixedPlatforms + * Fixed user platform + * @return Null if dialog aborted, else the new CHANGEABLE user platform list. + */ + public static Vector showDialog(Frame parentFrame, + Vector changablePlatforms, Vector fixedPlatforms) { + UserPlatformsDialog myDialog = new UserPlatformsDialog(parentFrame, + changablePlatforms, fixedPlatforms); + myDialog.setLocationRelativeTo(parentFrame); + + if (myDialog != null) { + myDialog.setVisible(true); + } + + return myDialog.changableUserPlatforms; + } + + private UserPlatformsDialog(Frame frame, Vector changablePlatforms, + Vector fixedPlatforms) { + super(frame, "Manage User Platforms", true); + + myParentFrame = frame; + myDialog = this; + + JPanel mainPane = new JPanel(); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + JButton button; + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Cancel"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + changableUserPlatforms = null; + dispose(); + } + }); + buttonPane.add(button); + + buttonPane.add(Box.createRigidArea(new Dimension(10, 0))); + + button = new JButton("OK"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + changableUserPlatforms = new Vector(); + for (String directory : changablePlatformsList.getItems()) { + File userPlatform = new File(directory); + if (userPlatform.exists() && userPlatform.isDirectory()) + changableUserPlatforms.add(userPlatform); + else + logger.fatal("Can't find user platform: " + userPlatform); + } + dispose(); + } + }); + buttonPane.add(button); + this.getRootPane().setDefaultButton(button); + + // LIST PART + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.X_AXIS)); + listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + JPanel listPane2 = new JPanel(); + listPane2.setLayout(new BoxLayout(listPane2, BoxLayout.Y_AXIS)); + + if (fixedPlatforms != null) { + fixedPlatformsList = new List(); + fixedPlatformsList.setEnabled(false); + listPane2.add(new JLabel("Fixed:")); + listPane2.add(fixedPlatformsList); + } + + listPane2.add(new JLabel("Changable:")); + listPane2.add(changablePlatformsList); + + listPane.add(listPane2); + + smallPane = new JPanel(); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.Y_AXIS)); + + button = new JButton("Move up"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int selectedIndex = changablePlatformsList.getSelectedIndex(); + if (selectedIndex <= 0) + return; + + File file = new File(changablePlatformsList.getItem(selectedIndex)); + + removeUserPlatform(selectedIndex); + addUserPlatform(file, selectedIndex - 1); + changablePlatformsList.select(selectedIndex - 1); + } + }); + smallPane.add(button); + + button = new JButton("Move down"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + int selectedIndex = changablePlatformsList.getSelectedIndex(); + if (selectedIndex < 0) + return; + if (selectedIndex >= changablePlatformsList.getItemCount() - 1) + return; + + File file = new File(changablePlatformsList.getItem(selectedIndex)); + removeUserPlatform(selectedIndex); + addUserPlatform(file, selectedIndex + 1); + changablePlatformsList.select(selectedIndex + 1); + } + }); + smallPane.add(button); + + smallPane.add(Box.createRigidArea(new Dimension(10, 10))); + + button = new JButton("Remove"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (changablePlatformsList.getSelectedIndex() < 0) + return; + + removeUserPlatform(changablePlatformsList.getSelectedIndex()); + } + }); + smallPane.add(button); + + listPane.add(smallPane); + + // ADD/REMOVE PART + JPanel addRemovePane = new JPanel(); + addRemovePane.setLayout(new BoxLayout(addRemovePane, BoxLayout.X_AXIS)); + addRemovePane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + button = new JButton("View resulting config"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Create default configuration + PlatformConfig config = new PlatformConfig(); + try { + config.appendConfig(new File(GUI.PLATFORM_DEFAULT_CONFIG_FILENAME)); + } catch (FileNotFoundException ex) { + logger.fatal("Could not find default platform config file: " + + GUI.PLATFORM_DEFAULT_CONFIG_FILENAME); + return; + } catch (IOException ex) { + logger.fatal("Error when reading default platform config file: " + + GUI.PLATFORM_DEFAULT_CONFIG_FILENAME); + return; + } + + // Add the fixed platform configurations + if (fixedPlatformsList != null) { + for (String userPlatform : fixedPlatformsList.getItems()) { + File userPlatformConfig = new File(userPlatform + File.separatorChar + + GUI.PLATFORM_CONFIG_FILENAME); + try { + config.appendConfig(userPlatformConfig); + } catch (Exception ex) { + logger.fatal("Error when merging configurations: " + ex); + return; + } + } + } + + // Add the user platform configurations + for (String userPlatform : changablePlatformsList.getItems()) { + File userPlatformConfig = new File(userPlatform + File.separatorChar + + GUI.PLATFORM_CONFIG_FILENAME); + try { + config.appendConfig(userPlatformConfig); + } catch (Exception ex) { + logger.fatal("Error when merging configurations: " + ex); + return; + } + } + + // Show merged configuration + ConfigViewer.showDialog(myParentFrame, config); + } + }); + addRemovePane.add(button); + + addRemovePane.add(Box.createRigidArea(new Dimension(10, 0))); + + button = new JButton("Add manually"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + String newUserPlatformPath = JOptionPane.showInputDialog(myDialog, + "Enter path to user platform", "Enter path", + JOptionPane.QUESTION_MESSAGE); + if (newUserPlatformPath != null) { + addUserPlatform(new File(newUserPlatformPath)); + } + } + }); + addRemovePane.add(button); + + addRemovePane.add(Box.createRigidArea(new Dimension(10, 0))); + + button = new JButton("Browse"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new java.io.File(".")); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setDialogTitle("Select user platform"); + + if (fc.showOpenDialog(myDialog) == JFileChooser.APPROVE_OPTION) { + addUserPlatform(fc.getSelectedFile()); + } + } + }); + addRemovePane.add(button); + + // Add components + Container contentPane = getContentPane(); + mainPane.add(listPane); + mainPane.add(addRemovePane); + contentPane.add(mainPane, BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + // Add fixed user platforms if any + if (fixedPlatforms != null) { + for (File userPlatform : fixedPlatforms) { + fixedPlatformsList.add(userPlatform.getPath()); + } + } + + // Add already existing user platforms + for (File userPlatform : changablePlatforms) { + addUserPlatform(userPlatform); + } + + pack(); + } + + private void addUserPlatform(File userPlatform) { + addUserPlatform(userPlatform, changablePlatformsList.getItemCount()); + } + + private void addUserPlatform(File userPlatform, int index) { + // Check that file exists, is a directory and contains the correct files + if (!userPlatform.exists()) { + logger.fatal("Can't find user platform: " + userPlatform); + return; + } + if (!userPlatform.isDirectory()) { + logger.fatal("User platform is not a directory: " + userPlatform); + return; + } + + File userPlatformConfigFile = new File(userPlatform.getPath() + + File.separatorChar + GUI.PLATFORM_CONFIG_FILENAME); + if (!userPlatformConfigFile.exists()) { + logger.fatal("User platform has no configuration file: " + + userPlatformConfigFile); + return; + } + + changablePlatformsList.add(userPlatform.getPath(), index); + } + + private void removeUserPlatform(int index) { + changablePlatformsList.remove(index); + } + +} + +/** + * Modal frame that shows all keys with their respective values of a given class + * configuration. + * + * @author Fredrik Osterlind + */ +class ConfigViewer extends JDialog { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(ConfigViewer.class); + + public static void showDialog(Frame parentFrame, PlatformConfig config) { + ConfigViewer myDialog = new ConfigViewer(parentFrame, config); + myDialog.setLocationRelativeTo(parentFrame); + + if (myDialog != null) { + myDialog.setVisible(true); + } + } + + private ConfigViewer(Frame frame, PlatformConfig config) { + super(frame, "Current class configuration", true); + + JPanel mainPane = new JPanel(new BorderLayout()); + JLabel label; + JButton button; + + // BOTTOM BUTTON PART + JPanel buttonPane = new JPanel(); + buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.X_AXIS)); + buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + buttonPane.add(Box.createHorizontalGlue()); + + button = new JButton("Close"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + buttonPane.add(button); + + // LIST PART + JPanel keyPane = new JPanel(); + keyPane.setLayout(new BoxLayout(keyPane, BoxLayout.Y_AXIS)); + mainPane.add(keyPane, BorderLayout.WEST); + + JPanel valuePane = new JPanel(); + valuePane.setLayout(new BoxLayout(valuePane, BoxLayout.Y_AXIS)); + mainPane.add(valuePane, BorderLayout.CENTER); + + label = new JLabel("KEY"); + label.setForeground(Color.RED); + keyPane.add(label); + label = new JLabel("VALUE"); + label.setForeground(Color.RED); + valuePane.add(label); + + Enumeration allPropertyNames = config.getPropertyNames(); + while (allPropertyNames.hasMoreElements()) { + String propertyName = allPropertyNames.nextElement(); + + keyPane.add(new JLabel(propertyName)); + valuePane.add(new JLabel(config.getStringValue(propertyName))); + } + + // Add components + Container contentPane = getContentPane(); + contentPane.add(new JScrollPane(mainPane), BorderLayout.CENTER); + contentPane.add(buttonPane, BorderLayout.SOUTH); + + pack(); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Battery.java b/tools/cooja/java/se/sics/cooja/interfaces/Battery.java new file mode 100644 index 000000000..45c8ee71f --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Battery.java @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Battery.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import java.util.*; + +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * A Battery represents the energy source for a mote. This implementation has no + * connection with any underlying simulated software, hence a mote does not know + * the current energy levels. + *

+ * This Battery decreases current energy left each tick depending on the current + * mote state. If the mote is sleeping all passive interfaces' energy + * consumptions will be summed up and detracted from the current energy. If the + * mote is awake both the active and passive interfaces' energy consumptions + * will be used. Also, the energy used by the CPU (depends on mote state) will + * be detracted each tick. + *

+ * This observable is changed and notifies observers every time the energy left + * is changed. When current energy left has decreased below 0 the mote state is + * set to dead. + * + * @see MoteInterface + * @see MoteInterface#energyConsumptionPerTick() + * + * @author Fredrik Osterlind + */ +@ClassDescription("Battery") +public class Battery extends MoteInterface implements PassiveMoteInterface { + + /** + * Approximate energy consumption of a mote's CPU in active mode (mA). ESB + * measured energy consumption is 1.49 mA. + */ + public final double ENERGY_CONSUMPTION_AWAKE_mA; + + /** + * Approximate energy consumption of a mote's CPU in low power mode (mA). ESB + * measured energy consumption is 1.34 mA. + */ + public final double ENERGY_CONSUMPTION_LPM_mA; + + /** + * Initial energy of battery in milli coulomb (mQ). ESB mote: 3 regular AA + * batteries, each ~1.25 Ah. 3 * 1.25 Ah = 3 * 1.25 * 3600 Q = 13,500 Q = + * 13,500,000 mQ + */ + public final double INITIAL_ENERGY; + + private double energyConsumptionLPMPerTick = -1.0; + private double energyConsumptionAwakePerTick = -1.0; + + private Mote mote = null; + private static Logger logger = Logger.getLogger(Battery.class); + + private double myEnergy; + private boolean hasInfiniteEnergy; + + /** + * Creates a new battery connected to given mote. + * + * @see #INITIAL_ENERGY + * @param mote + * Mote holding battery + */ + public Battery(Mote mote) { + // Read class configurations of this mote type + ENERGY_CONSUMPTION_AWAKE_mA = mote.getType().getConfig() + .getDoubleValue(Battery.class, "CPU_AWAKE_mA"); + ENERGY_CONSUMPTION_LPM_mA = mote.getType().getConfig().getDoubleValue( + Battery.class, "CPU_LPM_mA"); + INITIAL_ENERGY = mote.getType().getConfig().getDoubleValue( + Battery.class, "INITIAL_ENERGY_mQ"); + hasInfiniteEnergy = mote.getType().getConfig().getBooleanValue( + Battery.class, "INFINITE_ENERGY_bool"); + + if (energyConsumptionAwakePerTick < 0) { + energyConsumptionAwakePerTick = ENERGY_CONSUMPTION_AWAKE_mA + * mote.getSimulation().getTickTimeInSeconds(); + energyConsumptionLPMPerTick = ENERGY_CONSUMPTION_LPM_mA + * mote.getSimulation().getTickTimeInSeconds(); + } + + this.mote = mote; + myEnergy = INITIAL_ENERGY; + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + // If infinite energy, do nothing + if (hasInfiniteEnergy) + return; + + // If mote is dead, do nothing + if (mote.getState() == Mote.STATE_DEAD) + return; + + // Check mote state + if (mote.getState() == Mote.STATE_LPM) { + // Mote is sleeping. Sum up energy usage. + double totalEnergyConsumption = 0.0; + totalEnergyConsumption += energyConsumptionLPMPerTick; + + for (MoteInterface passiveInterface : mote.getInterfaces() + .getAllPassiveInterfaces()) { + totalEnergyConsumption += passiveInterface.energyConsumptionPerTick(); + } + + decreaseEnergy(totalEnergyConsumption); + } else { + // Mote is awake. Sum up energy usage. + double totalEnergyConsumption = 0.0; + totalEnergyConsumption += energyConsumptionAwakePerTick; + + for (MoteInterface activeInterface : mote.getInterfaces() + .getAllActiveInterfaces()) { + totalEnergyConsumption += activeInterface.energyConsumptionPerTick(); + } + for (MoteInterface passiveInterface : mote.getInterfaces() + .getAllPassiveInterfaces()) { + totalEnergyConsumption += passiveInterface.energyConsumptionPerTick(); + } + + decreaseEnergy(totalEnergyConsumption); + } + + // Check if we are out of energy + if (getCurrentEnergy() <= 0.0) { + setChanged(); + notifyObservers(); + mote.setState(Mote.STATE_DEAD); + } + } + + /** + * @param inf + * Set infinite energy state + */ + public void setInfiniteEnergy(boolean inf) { + hasInfiniteEnergy = inf; + + setChanged(); + notifyObservers(); + } + + /** + * @return True if this battery has inifinite energy + */ + public boolean hasInfiniteEnergy() { + return hasInfiniteEnergy; + } + + /** + * @return Initial energy + */ + public double getInitialEnergy() { + return INITIAL_ENERGY; + } + + /** + * @return Current energy left + */ + public double getCurrentEnergy() { + return myEnergy; + } + + private void decreaseEnergy(double consumption) { + if (!hasInfiniteEnergy) { + myEnergy -= consumption; + setChanged(); + notifyObservers(); + } + } + + public JPanel getInterfaceVisualizer() { + // Battery energy left + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + final JLabel energyLabel = new JLabel(""); + final JLabel energyPercentLabel = new JLabel(""); + + if (hasInfiniteEnergy()) { + energyLabel.setText("INFINITE"); + energyPercentLabel.setText(""); + } else { + energyLabel.setText("Energy left (mQ) = " + getCurrentEnergy()); + energyPercentLabel.setText("Energy left (%) = " + + (getCurrentEnergy() / getInitialEnergy() * 100) + "%"); + } + + panel.add(energyLabel); + panel.add(energyPercentLabel); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + if (hasInfiniteEnergy()) { + energyLabel.setText("INFINITE"); + energyPercentLabel.setText(""); + } else { + energyLabel.setText("Energy left (mQ) = " + getCurrentEnergy()); + energyPercentLabel.setText("Energy left (%) = " + + (getCurrentEnergy() / getInitialEnergy() * 100) + "%"); + } + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + // The battery itself does not require any power. + return 0.0; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // Infinite boolean + element = new Element("infinite"); + element.setText(Boolean.toString(hasInfiniteEnergy)); + config.add(element); + + return config; + } + + public void setConfigXML(Collection configXML) { + for (Element element : configXML) { + if (element.getName().equals("infinite")) { + hasInfiniteEnergy = Boolean.parseBoolean(element.getText()); + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Beeper.java b/tools/cooja/java/se/sics/cooja/interfaces/Beeper.java new file mode 100644 index 000000000..90d4f9fad --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Beeper.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Beeper.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A Beeper represents a mote beeper. An implementation should notify all + * observers when the beeper changes state and typically requires more energy if + * beeping than when quiet. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Beeper") +public abstract class Beeper extends MoteInterface { + + /** + * @return True if beeper is beeping + */ + public abstract boolean isBeeping(); +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Button.java b/tools/cooja/java/se/sics/cooja/interfaces/Button.java new file mode 100644 index 000000000..df060b83c --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Button.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Button.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A Button represents a mote button. An implementation should notify all + * observers when the button changes state, and may simulate external interrupts + * by waking up a mote if button state changes. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Button") +public abstract class Button extends MoteInterface { + + /** + * Clicks button. Button will be pressed for some time and then automatically + * released. + */ + public abstract void clickButton(); + + /** + * Releases button (if pressed). + */ + public abstract void releaseButton(); + + /** + * Presses button (if not already pressed). + */ + public abstract void pressButton(); + + /** + * @return True if button is pressed + */ + public abstract boolean isPressed(); +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Clock.java b/tools/cooja/java/se/sics/cooja/interfaces/Clock.java new file mode 100644 index 000000000..5e60202fd --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Clock.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Clock.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A Clock represents a mote's internal clock. Notice that the overall + * simulation time and the mote's own time may differ. + * + * This observable never needs to update. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Clock") +public abstract class Clock extends MoteInterface { + + /** + * Set mote's time to given time. + * + * @param newTime + * New time + */ + public abstract void setTime(int newTime); + + /** + * @return Current time + */ + public abstract int getTime(); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/IPAddress.java b/tools/cooja/java/se/sics/cooja/interfaces/IPAddress.java new file mode 100644 index 000000000..59b4aceb5 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/IPAddress.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: IPAddress.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A IPAdress represents a mote Internet address. An implementation should notify all + * observers if the address is set or changed. + * + * @author Fredrik Osterlind + */ +@ClassDescription("IPv4 Address") +public abstract class IPAddress extends MoteInterface { + + /** + * Get current IP address on the form a.b.c.d. + * @return IP address string + */ + public abstract String getIPString(); + + /** + * Change/Set IP address. + * @param ipAddress IP string on the form a.b.c.d + */ + public abstract void setIPString(String ipAddress); + + /** + * Change/Set IP address. + * @param a First byte of IP address + * @param b Second byte of IP address + * @param c Third byte of IP address + * @param d Fourth byte of IP address + */ + public abstract void setIPNumber(char a, char b, char c, char d); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/LED.java b/tools/cooja/java/se/sics/cooja/interfaces/LED.java new file mode 100644 index 000000000..9e384c514 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/LED.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: LED.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A LED represents three mote LEDs (green, yellow and red). An implementation should notify all + * observers if any of the LEDs states are changed. + * + * @author Fredrik Osterlind + */ +@ClassDescription("LEDs") +public abstract class LED extends MoteInterface { + + /** + * @return True if any LED is on, false otherwise + */ + public abstract boolean isAnyOn(); + + /** + * @return True if green LED is on, false otherwise + */ + public abstract boolean isGreenOn(); + + /** + * @return True if yellow LED is on, false otherwise + */ + public abstract boolean isYellowOn(); + + /** + * @return True if red LED is on, false otherwise + */ + public abstract boolean isRedOn(); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Log.java b/tools/cooja/java/se/sics/cooja/interfaces/Log.java new file mode 100644 index 000000000..457a0a3ef --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Log.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Log.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A Log represents a mote logging output. An implementation should notify all + * observers whenever new logging output is available. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Log Output") +public abstract class Log extends MoteInterface { + + /** + * @return Last log messages available + */ + public abstract String getLastLogMessages(); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/MoteID.java b/tools/cooja/java/se/sics/cooja/interfaces/MoteID.java new file mode 100644 index 000000000..bc94c5136 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/MoteID.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteID.java,v 1.1 2006/08/21 12:12:58 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A MoteID represents a mote ID number. An implementation should notify all + * observers if the mote ID is set or changed. + * + * @author Fredrik Osterlind + */ +@ClassDescription("ID") +public abstract class MoteID extends MoteInterface { + + /** + * @return Current mote ID number + */ + public abstract int getMoteID(); + + /** + * Sets mote ID to given number. + * @param newID New mote ID number + */ + public abstract void setMoteID(int newID); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/PIR.java b/tools/cooja/java/se/sics/cooja/interfaces/PIR.java new file mode 100644 index 000000000..70f90aaee --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/PIR.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: PIR.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A PIR represents a passive infrared sensor. An implementation should notify all + * observers if the PIR discovers any changes. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Passive IR") +public abstract class PIR extends MoteInterface { + + /** + * Simulates a change in the PIR sensor. + */ + public abstract void triggerChange(); + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Position.java b/tools/cooja/java/se/sics/cooja/interfaces/Position.java new file mode 100644 index 000000000..c0bd536cb --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Position.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Position.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import java.text.NumberFormat; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +/** + * A Position represents the simulated 3D position of a mote. This + * implementation has no connection with any underlying simulated software, + * hence a mote does not know its current position. + *

+ * This observable is changed and notifies observers whenever new coordinates + * are set. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Position") +public class Position extends MoteInterface { + private static Logger logger = Logger.getLogger(Position.class); + private Mote mote = null; + private double[] coords = new double[3]; + + /** + * Creates a position for given mote with coordinates (x=0, y=0, z=0). + * + * @param mote + * Led's mote. + * @see Mote + * @see se.sics.cooja.MoteInterfaceHandler + */ + public Position(Mote mote) { + this.mote = mote; + + coords[0] = 0.0f; + coords[1] = 0.0f; + coords[2] = 0.0f; + } + + /** + * Updates coordiantes of associated mote to (x,y,z). + * + * @param x + * New X coordinate + * @param y + * New Y coordinate + * @param z + * New Z coordinate + */ + public void setCoordinates(double x, double y, double z) { + coords[0] = x; + coords[1] = y; + coords[2] = z; + + this.setChanged(); + this.notifyObservers(mote); + } + + /** + * @return X coordinate + */ + public double getXCoordinate() { + return coords[0]; + } + + /** + * @return Y coordinate + */ + public double getYCoordinate() { + return coords[1]; + } + + /** + * @return Z coordinate + */ + public double getZCoordinate() { + return coords[2]; + } + + /** + * Calculates distance from this position to given position. + * + * @param pos + * Compared position + * @return Distance + */ + public double getDistanceTo(Position pos) { + return Math.sqrt(Math.abs(coords[0] - pos.getXCoordinate()) + * Math.abs(coords[0] - pos.getXCoordinate()) + + Math.abs(coords[1] - pos.getYCoordinate()) + * Math.abs(coords[1] - pos.getYCoordinate()) + + Math.abs(coords[2] - pos.getZCoordinate()) + * Math.abs(coords[2] - pos.getZCoordinate())); + } + + /** + * Calculates distance from associated mote to another mote. + * + * @param m + * Another mote + * @return Distance + */ + public double getDistanceTo(Mote m) { + return getDistanceTo(m.getInterfaces().getPosition()); + } + + public void doActionsBeforeTick() { + // Nothing to do + } + + public void doActionsAfterTick() { + // Nothing to do + } + + public JPanel getInterfaceVisualizer() { + + // Location + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + final NumberFormat form = NumberFormat.getNumberInstance(); + + final JLabel positionLabel = new JLabel(); + positionLabel.setText("(" + form.format(getXCoordinate()) + "," + + form.format(getYCoordinate()) + "," + form.format(getZCoordinate()) + + ")"); + + panel.add(positionLabel); + + Observer observer; + this.addObserver(observer = new Observer() { + public void update(Observable obs, Object obj) { + positionLabel.setText("(" + form.format(getXCoordinate()) + "," + + form.format(getYCoordinate()) + "," + + form.format(getZCoordinate()) + ")"); + } + }); + + // Saving observer reference for releaseInterfaceVisualizer + panel.putClientProperty("intf_obs", observer); + + return panel; + } + + public void releaseInterfaceVisualizer(JPanel panel) { + Observer observer = (Observer) panel.getClientProperty("intf_obs"); + if (observer == null) { + logger.fatal("Error when releasing panel, observer is null"); + return; + } + + this.deleteObserver(observer); + } + + public double energyConsumptionPerTick() { + return 0.0; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // X coordinate + element = new Element("x"); + element.setText(Double.toString(getXCoordinate())); + config.add(element); + + // Y coordinate + element = new Element("y"); + element.setText(Double.toString(getYCoordinate())); + config.add(element); + + // Z coordinate + element = new Element("z"); + element.setText(Double.toString(getZCoordinate())); + config.add(element); + + return config; + } + + public void setConfigXML(Collection configXML) { + double x = 0, y = 0, z = 0; + + for (Element element : configXML) { + if (element.getName().equals("x")) { + x = Double.parseDouble(element.getText()); + } + + if (element.getName().equals("y")) { + y = Double.parseDouble(element.getText()); + } + + if (element.getName().equals("z")) { + z = Double.parseDouble(element.getText()); + } + } + + setCoordinates(x, y, z); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/interfaces/Radio.java b/tools/cooja/java/se/sics/cooja/interfaces/Radio.java new file mode 100644 index 000000000..c530386f8 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/interfaces/Radio.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Radio.java,v 1.1 2006/08/21 12:12:59 fros4943 Exp $ + */ + +package se.sics.cooja.interfaces; + +import se.sics.cooja.*; + +/** + * A Radio represents a mote radio transceiver. An implementation should notify + * all observers both when packets are received and transmitted. The static + * constants should be used for describing the radio status when the observers + * are notified. + * + * @see #SENT_NOTHING + * @see #SENT_SOMETHING + * @see #HEARS_NOTHING + * @see #HEARS_PACKET + * @see #HEARS_NOISE + * @author Fredrik Osterlind + */ +@ClassDescription("Packet Radio") +public abstract class Radio extends MoteInterface { + + /** + * This radio has not sent anything last tick. + */ + public static final int SENT_NOTHING = 1; + + /** + * This radio has sent something last tick. + */ + public static final int SENT_SOMETHING = 2; + + /** + * This radio is not hearing anything. + */ + public static final int HEARS_NOTHING = 1; + + /** + * This radio is hearing exactly one packet right now. + */ + public static final int HEARS_PACKET = 2; + + /** + * This radio is hearing a lot of noise right now (may be several packets). + */ + public static final int HEARS_NOISE = 3; + + /** + * @return Last packet data sent from this radio. + */ + public abstract byte[] getLastPacketSent(); + + /** + * @return Last packet data received by this radio. + */ + public abstract byte[] getLastPacketReceived(); + + /** + * Send given packet data to this radio. The radio may or may not receive the + * data correctly depending on the current listen state. + * + * @param data + */ + public abstract void receivePacket(byte[] data); + + /** + * @return Current send state + * @see #SENT_NOTHING + * @see #SENT_SOMETHING + */ + public abstract int getSendState(); + + /** + * @return Current listen state + * + * @see #setListenState(int) + * @see #HEARS_NOTHING + * @see #HEARS_PACKET + * @see #HEARS_NOISE + */ + public abstract int getListenState(); + + /** + * Changes listen state to given state + * @param newState + * New listen state + * + * @see #getListenState() + * @see #HEARS_NOTHING + * @see #HEARS_PACKET + * @see #HEARS_NOISE + */ + public abstract void setListenState(int newState); + + /** + * Advances listen state one step. If listen state was 'hears nothing', it + * will become 'hears packet'. If it was 'hears packet', it will become 'hears + * noise'. If it was 'hears noise', it will stay that way. + * + * @see #getListenState() + */ + public abstract void advanceListenState(); + +} diff --git a/tools/cooja/java/se/sics/cooja/ipdistributors/IdIPDistributor.java b/tools/cooja/java/se/sics/cooja/ipdistributors/IdIPDistributor.java new file mode 100644 index 000000000..b45262ee6 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/ipdistributors/IdIPDistributor.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: IdIPDistributor.java,v 1.1 2006/08/21 12:13:06 fros4943 Exp $ + */ + +package se.sics.cooja.ipdistributors; +import java.util.Vector; +import se.sics.cooja.*; + +/** + * Generates IP addresses on the form 10.[id/256 mod 256*256].[id mod 256].1. + * + * Observe! + * - ID must be set before this is called (otherwise IP=0.0.0.0). + * - Only supports 256*256 motes, (IPs will wrap if above). + * + * @author Fredrik Osterlind + */ +@ClassDescription("From ID (10.id.id.1)") +public class IdIPDistributor extends IPDistributor { + private Vector generatedIPAddresses; + + /** + * Creates a Id IP distributor. + * @param newMotes All motes which later will be assigned IP numbers. + */ + public IdIPDistributor(Vector newMotes) { + generatedIPAddresses = new Vector(); + + for (int i=0; i < newMotes.size(); i++) { + if (newMotes.get(i).getInterfaces().getMoteID() != null) { + int moteId = newMotes.get(i).getInterfaces().getMoteID().getMoteID(); + generatedIPAddresses.add("10." + + (moteId / 256 % (256*256)) + + "." + + (moteId % 256) + + ".1"); + } else + generatedIPAddresses.add("0.0.0.0"); + } + + } + + public String getNextIPAddress() { + if (generatedIPAddresses.size() > 0) + return generatedIPAddresses.remove(0); + else + return "0.0.0.0"; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/ipdistributors/RandomIPDistributor.java b/tools/cooja/java/se/sics/cooja/ipdistributors/RandomIPDistributor.java new file mode 100644 index 000000000..74ad8cd17 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/ipdistributors/RandomIPDistributor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: RandomIPDistributor.java,v 1.1 2006/08/21 12:13:06 fros4943 Exp $ + */ + +package se.sics.cooja.ipdistributors; +import java.util.Vector; +import se.sics.cooja.*; + +/** + * Generates IP addresses randomly on the form 10.10.[1-20].[1-20]. + * Nothing prevents several motes from getting the same IP number. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Random (10.10.?.?)") +public class RandomIPDistributor extends IPDistributor { + + /** + * Creates a random IP distributor. + * @param newMotes All motes which later will be assigned IP numbers. + */ + public RandomIPDistributor(Vector newMotes) { + // NOP + } + + public String getNextIPAddress() { + return "" + 10 + "." + 10 + "." + (Math.round(Math.random()*20) + 1) + "." + (Math.round(Math.random()*20) + 1); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/ipdistributors/SpatialIPDistributor.java b/tools/cooja/java/se/sics/cooja/ipdistributors/SpatialIPDistributor.java new file mode 100644 index 000000000..72763d092 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/ipdistributors/SpatialIPDistributor.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: SpatialIPDistributor.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.ipdistributors; +import java.util.Vector; +import se.sics.cooja.*; + +/** + * Generates spatial IP addresses on the form 10.[z-coord].[y-coord].[x-coord]. + * The smallest coordinate in each interval will be mapped onto address 1, + * and the biggest coordinate onto address 200. + * Nothing prevents several motes from getting the same IP number. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Spatial (10.x.y.z)") +public class SpatialIPDistributor extends IPDistributor { + private double lowestX, biggestX, lowestY, biggestY, lowestZ, biggestZ; + private Vector generatedIPAddresses; + + /** + * Creates a random IP distributor. + * @param newMotes All motes which later will be assigned IP numbers. + */ + public SpatialIPDistributor(Vector newMotes) { + lowestX = newMotes.get(0).getInterfaces().getPosition().getXCoordinate(); + biggestX = newMotes.get(0).getInterfaces().getPosition().getXCoordinate(); + lowestY = newMotes.get(0).getInterfaces().getPosition().getYCoordinate(); + biggestY = newMotes.get(0).getInterfaces().getPosition().getYCoordinate(); + lowestZ = newMotes.get(0).getInterfaces().getPosition().getZCoordinate(); + biggestZ = newMotes.get(0).getInterfaces().getPosition().getZCoordinate(); + + for (int i=0; i < newMotes.size(); i++) { + if (newMotes.get(i).getInterfaces().getPosition().getXCoordinate() < lowestX) + lowestX = newMotes.get(i).getInterfaces().getPosition().getXCoordinate(); + if (newMotes.get(i).getInterfaces().getPosition().getXCoordinate() > biggestX) + biggestX = newMotes.get(i).getInterfaces().getPosition().getXCoordinate(); + + if (newMotes.get(i).getInterfaces().getPosition().getYCoordinate() < lowestY) + lowestY = newMotes.get(i).getInterfaces().getPosition().getYCoordinate(); + if (newMotes.get(i).getInterfaces().getPosition().getYCoordinate() > biggestY) + biggestY = newMotes.get(i).getInterfaces().getPosition().getYCoordinate(); + + if (newMotes.get(i).getInterfaces().getPosition().getZCoordinate() < lowestZ) + lowestZ = newMotes.get(i).getInterfaces().getPosition().getZCoordinate(); + if (newMotes.get(i).getInterfaces().getPosition().getZCoordinate() > biggestZ) + biggestZ = newMotes.get(i).getInterfaces().getPosition().getZCoordinate(); + } + + generatedIPAddresses = new Vector(); + for (int i=0; i < newMotes.size(); i++) { + String ipAddress = "10."; + int partIP; + + // Z coord + partIP = (int) (1 + 199*(newMotes.get(i).getInterfaces().getPosition().getZCoordinate() - lowestZ) / (biggestZ - lowestZ)); + ipAddress = ipAddress.concat(partIP + "."); + + // Y coord + partIP = (int) (1 + 199*(newMotes.get(i).getInterfaces().getPosition().getYCoordinate() - lowestY) / (biggestY - lowestY)); + ipAddress = ipAddress.concat(partIP + "."); + + // X coord + partIP = (int) (1 + 199*(newMotes.get(i).getInterfaces().getPosition().getXCoordinate() - lowestX) / (biggestX - lowestX)); + ipAddress = ipAddress.concat(partIP + ""); + + generatedIPAddresses.add(ipAddress); + } + } + + public String getNextIPAddress() { + if (generatedIPAddresses.size() > 0) + return generatedIPAddresses.remove(0); + else + return "0.0.0.0"; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/motes/DummyMote.java b/tools/cooja/java/se/sics/cooja/motes/DummyMote.java new file mode 100644 index 000000000..da9cb72dc --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/motes/DummyMote.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: DummyMote.java,v 1.1 2006/08/21 12:13:12 fros4943 Exp $ + */ + +package se.sics.cooja.motes; + +import java.util.Collection; +import java.util.Observer; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; + +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.Position; + +/** + * A dummy mote is a purely Java-based mote, and can be used as an example of + * how to implement motes other than the usual Contiki mote. + * + * The dummy mote uses an empty section mote memory without any variable + * mappings. + * + * The mote interface handler has a position interface, added when the mote is + * constructed. + * + * When the dummy mote is ticked all (one!) interfaces are polled and a random + * variable decides if the position should be changed. The node never leaves the + * active state. + * + * @author Fredrik Osterlind + */ +public class DummyMote implements Mote { + + private static Logger logger = Logger.getLogger(DummyMote.class); + + private MoteType myType = null; + private SectionMoteMemory myMemory = null; + private MoteInterfaceHandler myInterfaceHandler = null; + private Simulation mySim = null; + + private Random myRandom = new Random(); + + /** + * Creates a new uninitialized dummy mote. + */ + public DummyMote() { + } + + /** + * Creates a new dummy mote of the given type in the given simulation. An + * empty mote memory and a position interface is added to this mote. + * + * @param moteType + * Mote type + * @param sim + * Simulation + */ + public DummyMote(MoteType moteType, Simulation sim) { + mySim = sim; + myType = moteType; + + // Create memory + myMemory = new SectionMoteMemory(new Properties()); + + // Create interface handler + myInterfaceHandler = new MoteInterfaceHandler(); + Position myPosition = new Position(this); + myPosition.setCoordinates(myRandom.nextDouble() * 100, myRandom + .nextDouble() * 100, myRandom.nextDouble() * 100); + myInterfaceHandler.addPassiveInterface(myPosition); + } + + public void setState(int newState) { + logger.fatal("Dummy mote can not change state"); + } + + public int getState() { + return Mote.STATE_ACTIVE; + } + + public void addStateObserver(Observer newObserver) { + } + + public void deleteStateObserver(Observer newObserver) { + } + + public MoteInterfaceHandler getInterfaces() { + return myInterfaceHandler; + } + + public void setInterfaces(MoteInterfaceHandler moteInterfaceHandler) { + myInterfaceHandler = moteInterfaceHandler; + } + + public MoteMemory getMemory() { + return myMemory; + } + + public void setMemory(MoteMemory memory) { + myMemory = (SectionMoteMemory) memory; + } + + public MoteType getType() { + return myType; + } + + public void setType(MoteType type) { + myType = type; + } + + public Simulation getSimulation() { + return mySim; + } + + public void setSimulation(Simulation simulation) { + this.mySim = simulation; + } + + public void tick(int simTime) { + + // Perform some dummy task + if (myRandom.nextDouble() > 0.9) { + // Move mote randomly + Position myPosition = myInterfaceHandler.getPosition(); + myPosition.setCoordinates(myPosition.getXCoordinate() + + myRandom.nextDouble() - 0.5, myPosition.getYCoordinate() + + myRandom.nextDouble() - 0.5, myPosition.getZCoordinate() + + myRandom.nextDouble() - 0.5); + } + } + + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // We need to save the mote type identifier + element = new Element("motetype_identifier"); + element.setText(getType().getIdentifier()); + config.add(element); + + // The position interface should also save its config + element = new Element("interface_config"); + element.setText(myInterfaceHandler.getPosition().getClass().getName()); + + Collection interfaceXML = myInterfaceHandler.getPosition().getConfigXML(); + if (interfaceXML != null) { + element.addContent(interfaceXML); + config.add(element); + } + + return config; + } + + public boolean setConfigXML(Simulation simulation, + Collection configXML) { + mySim = simulation; + myMemory = new SectionMoteMemory(new Properties()); + myInterfaceHandler = new MoteInterfaceHandler(); + myInterfaceHandler.addPassiveInterface(new Position(this)); + + for (Element element : configXML) { + String name = element.getName(); + + if (name.equals("motetype_identifier")) { + myType = simulation.getMoteType(element.getText()); + } else if (name.equals("interface_config")) { + Class moteInterfaceClass = GUI.currentGUI + .tryLoadClass(this, MoteInterface.class, element.getText().trim()); + + if (moteInterfaceClass == null) { + logger.warn("Can't find mote interface class: " + element.getText()); + return false; + } + + MoteInterface moteInterface = myInterfaceHandler + .getInterfaceOfType(moteInterfaceClass); + moteInterface.setConfigXML(element.getChildren()); + } + + } + return true; + } + + public String toString() { + if (getInterfaces().getMoteID() != null) { + return "Dummy Mote, ID=" + getInterfaces().getMoteID().getMoteID(); + } else + return "Dummy Mote, ID=null"; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/motes/DummyMoteType.java b/tools/cooja/java/se/sics/cooja/motes/DummyMoteType.java new file mode 100644 index 000000000..99f4f13e7 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/motes/DummyMoteType.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: DummyMoteType.java,v 1.1 2006/08/21 12:13:12 fros4943 Exp $ + */ + +package se.sics.cooja.motes; + +import java.util.*; + +import javax.swing.*; + +import org.apache.log4j.Logger; +import org.jdom.Element; + +import se.sics.cooja.*; + +@ClassDescription("Dummy Mote Type") +public class DummyMoteType implements MoteType { + private static Logger logger = Logger.getLogger(DummyMoteType.class); + + // Mote type specific data + private String identifier = null; + private String description = null; + + public DummyMoteType() { + } + + public DummyMoteType(String identifier) { + this.identifier = identifier; + description = "Dummy Mote Type #" + identifier; + } + + public Mote generateMote(Simulation simulation) { + return new DummyMote(this, simulation); + } + + public boolean configureAndInit(JFrame parentFrame, Simulation simulation) { + + if (identifier == null) { + // Create unique identifier + int counter = 0; + boolean identifierOK = false; + while (!identifierOK) { + counter++; + identifier = "dummy" + counter; + identifierOK = true; + + // Check if identifier is already used by some other type + for (MoteType existingMoteType : simulation.getMoteTypes()) { + if (existingMoteType != this + && existingMoteType.getIdentifier().equals(identifier)) { + identifierOK = false; + break; + } + } + } + + if (description == null) { + // Create description + description = "Dummy Mote Type #" + counter; + } + + } + + if (description == null) { + // Create description + description = "Dummy Mote Type #" + identifier; + } + + return true; + } + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public JPanel getTypeVisualizer() { + return null; + } + + public PlatformConfig getConfig() { + return null; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + + Element element; + + // Identifier + element = new Element("identifier"); + element.setText(getIdentifier()); + config.add(element); + + // Description + element = new Element("description"); + element.setText(getDescription()); + config.add(element); + + return config; + } + + public boolean setConfigXML(Simulation simulation, Collection configXML) { + for (Element element : configXML) { + String name = element.getName(); + + if (name.equals("identifier")) { + identifier = element.getText(); + } else if (name.equals("description")) { + description = element.getText(); + } else { + logger.fatal("Unrecognized entry in loaded configuration: " + name); + } + } + + boolean createdOK = configureAndInit(GUI.frame, simulation); + return createdOK; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/LogListener.java b/tools/cooja/java/se/sics/cooja/plugins/LogListener.java new file mode 100644 index 000000000..7b45f007a --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/LogListener.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: LogListener.java,v 1.1 2006/08/21 12:13:08 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.Insets; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.Log; + +/** + * A simple mote log listener. + * When instantiated, is registers as a listener on all currently existing + * motes' log interfaces. (Observe that if new motes are added to a simulation, + * a new log listener must be created to listen to those motes also). + * + * @author Fredrik Osterlind + */ +@ClassDescription("Log Listener") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class LogListener extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(LogListener.class); + + private JTextArea logTextArea; + private Observer logObserver; + private Simulation simulation; + + /** + * Create a new simulation control panel. + * + * @param simulationToControl Simulation to control + */ + public LogListener(final Simulation simulationToControl) { + super("Log Listener - Listening on ?? mote logs"); + simulation = simulationToControl; + int nrLogs = 0; + + // Log observer + logObserver = new Observer() { + public void update(Observable obs, Object obj) { + logTextArea.append("\n"); + + Mote mote = (Mote) obj; + Log moteLogInterface = (Log) obs; + String outputString = "TIME:" + simulation.getSimulationTime() + "\t"; + if (mote != null && mote.getInterfaces().getMoteID() != null) { + outputString = outputString.concat("ID:" + mote.getInterfaces().getMoteID().getMoteID() + "\t"); + } + outputString = outputString.concat(moteLogInterface.getLastLogMessages()); + + logTextArea.append(outputString); + logTextArea.setCaretPosition(logTextArea.getDocument().getLength()); + } + }; + + // Register as loglistener on all currently active motes + for (int i=0; i < simulation.getMotesCount(); i++) { + if (simulation.getMote(i).getInterfaces().getLog() != null) { + simulation.getMote(i).getInterfaces().getLog().addObserver(logObserver); + nrLogs++; + } + } + + // Main panel + logTextArea = new JTextArea(8,50); + logTextArea.setMargin(new Insets(5,5,5,5)); + logTextArea.setEditable(false); + logTextArea.setCursor(null); + + setContentPane(new JScrollPane(logTextArea)); + setTitle("Log Listener - Listening on " + nrLogs + " mote logs"); + pack(); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + // Remove log observer from all log interfaces + for (int i=0; i < simulation.getMotesCount(); i++) { + if (simulation.getMote(i).getInterfaces().getLog() != null) + simulation.getMote(i).getInterfaces().getLog().deleteObserver(logObserver); + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/MoteInformation.java b/tools/cooja/java/se/sics/cooja/plugins/MoteInformation.java new file mode 100644 index 000000000..810f3a74f --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/MoteInformation.java @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteInformation.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * MoteInformation is a simple information window for motes. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Mote Information") +@VisPluginType(VisPluginType.MOTE_PLUGIN) +public class MoteInformation extends VisPlugin { + private static Logger logger = Logger.getLogger(MoteInformation.class); + + private static final long serialVersionUID = 1L; + + private Mote mote; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private final JLabel stateLabel; + + private Observer stateObserver; + private Vector visibleMoteInterfaces = new Vector(); + + /** + * Create a new mote information window. + * + * @param moteToView Mote to view + */ + public MoteInformation(Mote moteToView) { + super("Mote Information (" + moteToView + ")"); + + mote = moteToView; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + + // Remove mote button + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Remove mote"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.WEST, label); + + JButton button = new JButton("Remove"); + button.setActionCommand("removeMote"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + GUI.currentSimulation.removeMote(mote); + dispose(); + } + }); + + smallPane.add(BorderLayout.EAST, button); + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + + // Visualize mote type + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("-- STATE --"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.WEST, label); + if (mote.getState() == Mote.STATE_ACTIVE) + label = new JLabel("active"); + else if (mote.getState() == Mote.STATE_LPM) + label = new JLabel("low power mode"); + else + label = new JLabel("dead"); + + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + stateLabel = label; + + smallPane.add(BorderLayout.EAST, label); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + + + // Visualize mote type + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("-- MOTE TYPE --"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.NORTH, label); + JPanel moteVis = mote.getType().getTypeVisualizer(); + if (moteVis != null) { + moteVis.setBorder(BorderFactory.createEtchedBorder()); + smallPane.add(moteVis); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + } + + + // All interfaces + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("-- INTERFACES --"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.NORTH, label); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,10))); + + for (int i=0; i < mote.getInterfaces().getAllActiveInterfaces().size(); i++) { + smallPane = new JPanel(); + smallPane.setLayout(new BorderLayout()); + + MoteInterface moteInterface = mote.getInterfaces().getAllActiveInterfaces().get(i); + String interfaceDescription = GUI.getDescriptionOf(moteInterface); + JPanel interfaceVisualizer = moteInterface.getInterfaceVisualizer(); + label = new JLabel(interfaceDescription); + label.setAlignmentX(JLabel.CENTER_ALIGNMENT); + smallPane.add(BorderLayout.NORTH, label); + + if (interfaceVisualizer != null) { + interfaceVisualizer.setBorder(BorderFactory.createEtchedBorder()); + smallPane.add(BorderLayout.CENTER, interfaceVisualizer); + + // Tag each visualized interface to easier release them later + interfaceVisualizer.putClientProperty("my_interface", moteInterface); + visibleMoteInterfaces.add(interfaceVisualizer); + } + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + } + for (int i=0; i < mote.getInterfaces().getAllPassiveInterfaces().size(); i++) { + smallPane = new JPanel(); + smallPane.setLayout(new BorderLayout()); + + MoteInterface moteInterface = mote.getInterfaces().getAllPassiveInterfaces().get(i); + String interfaceDescription = GUI.getDescriptionOf(moteInterface); + + JPanel interfaceVisualizer = moteInterface.getInterfaceVisualizer(); + label = new JLabel(interfaceDescription); + label.setAlignmentX(JLabel.CENTER_ALIGNMENT); + smallPane.add(BorderLayout.NORTH, label); + + if (interfaceVisualizer != null) { + interfaceVisualizer.setBorder(BorderFactory.createEtchedBorder()); + smallPane.add(BorderLayout.CENTER, interfaceVisualizer); + + // Tag each visualized interface to easier release them later + interfaceVisualizer.putClientProperty("my_interface", moteInterface); + visibleMoteInterfaces.add(interfaceVisualizer); + } + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + } + + + this.setContentPane(new JScrollPane(mainPane, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + pack(); + setPreferredSize(new Dimension(350,500)); + setSize(new Dimension(350,500)); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + // Register as state observer to detect if mote changes state + mote.addStateObserver(stateObserver = new Observer() { + public void update(Observable obs, Object obj) { + if (mote.getState() == Mote.STATE_ACTIVE) + stateLabel.setText("active"); + else if (mote.getState() == Mote.STATE_LPM) + stateLabel.setText("low power mode"); + else + stateLabel.setText("dead"); + } + }); + } + + public void closePlugin() { + // Remove state observer + mote.deleteStateObserver(stateObserver); + + // Release all interface visualizations + for (JPanel interfaceVisualization: visibleMoteInterfaces) { + MoteInterface moteInterface = (MoteInterface) interfaceVisualization.getClientProperty("my_interface"); + if (moteInterface != null && interfaceVisualization != null) + moteInterface.releaseInterfaceVisualizer(interfaceVisualization); + else + logger.warn("Could not release panel"); + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/MoteInterfaceViewer.java b/tools/cooja/java/se/sics/cooja/plugins/MoteInterfaceViewer.java new file mode 100644 index 000000000..68d10bb06 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/MoteInterfaceViewer.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteInterfaceViewer.java,v 1.1 2006/08/21 12:13:09 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +import se.sics.cooja.*; + +/** + * MoteInterfaceViewer allows a user to select and view information about a node's interfaces. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Mote Interface Viewer") +@VisPluginType(VisPluginType.MOTE_PLUGIN) +public class MoteInterfaceViewer extends VisPlugin { + private static final long serialVersionUID = 1L; + + private Mote mote; + private MoteInterface selectedMoteInterface = null; + private JPanel currentInterfaceVisualizer = null; + + /** + * Create a new mote interface viewer. + * + * @param moteToView Mote to view + */ + public MoteInterfaceViewer(Mote moteToView) { + super("Mote Interface Viewer (" + moteToView + ")"); + mote = moteToView; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPane.setLayout(new BorderLayout()); + JPanel smallPane; + + // Select interface combo box + smallPane = new JPanel(new BorderLayout()); + + label = new JLabel("Select interface:"); + + final JComboBox selectInterfaceComboBox = new JComboBox(); + final JPanel interfacePanel = new JPanel(); + + for (int i=0; i < mote.getInterfaces().getAllActiveInterfaces().size(); i++) { + selectInterfaceComboBox.addItem(GUI.getDescriptionOf(mote.getInterfaces().getAllActiveInterfaces().get(i))); + } + for (int i=0; i < mote.getInterfaces().getAllPassiveInterfaces().size(); i++) { + selectInterfaceComboBox.addItem(GUI.getDescriptionOf(mote.getInterfaces().getAllPassiveInterfaces().get(i))); + } + + selectInterfaceComboBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + + // Release old interface visualizer if any + if (selectedMoteInterface != null && currentInterfaceVisualizer != null) + selectedMoteInterface.releaseInterfaceVisualizer(currentInterfaceVisualizer); + + // View selected interface if any + interfacePanel.removeAll(); + String interfaceDescription = (String) selectInterfaceComboBox.getSelectedItem(); + selectedMoteInterface = null; + for (int i=0; i < mote.getInterfaces().getAllActiveInterfaces().size(); i++) { + if (GUI.getDescriptionOf(mote.getInterfaces().getAllActiveInterfaces().get(i)).equals(interfaceDescription)) + selectedMoteInterface = mote.getInterfaces().getAllActiveInterfaces().get(i); + } + for (int i=0; i < mote.getInterfaces().getAllPassiveInterfaces().size(); i++) { + if (GUI.getDescriptionOf(mote.getInterfaces().getAllPassiveInterfaces().get(i)).equals(interfaceDescription)) + selectedMoteInterface = mote.getInterfaces().getAllPassiveInterfaces().get(i); + } + currentInterfaceVisualizer = selectedMoteInterface.getInterfaceVisualizer(); + if (currentInterfaceVisualizer != null) { + currentInterfaceVisualizer.setBorder(BorderFactory.createEtchedBorder()); + interfacePanel.add(BorderLayout.CENTER, currentInterfaceVisualizer); + currentInterfaceVisualizer.setVisible(true); + } else { + interfacePanel.add(new JLabel("No interface visualizer exists!")); + currentInterfaceVisualizer = null; + } + setSize(getSize()); + } + }); + selectInterfaceComboBox.setSelectedIndex(0); + + smallPane.add(BorderLayout.WEST, label); + smallPane.add(BorderLayout.EAST, selectInterfaceComboBox); + mainPane.add(BorderLayout.NORTH, smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,10))); + + // Add selected interface + interfacePanel.setLayout(new BorderLayout()); + if (selectInterfaceComboBox.getItemCount() > 0) { + selectInterfaceComboBox.setSelectedIndex(0); + selectInterfaceComboBox.dispatchEvent(new ActionEvent(selectInterfaceComboBox, ActionEvent.ACTION_PERFORMED, "")); + } + + mainPane.add(BorderLayout.CENTER, interfacePanel); + + this.setContentPane(new JScrollPane(mainPane, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + pack(); + setPreferredSize(new Dimension(350,300)); + setSize(new Dimension(350,300)); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + // Release old interface visualizer if any + if (selectedMoteInterface != null && currentInterfaceVisualizer != null) + selectedMoteInterface.releaseInterfaceVisualizer(currentInterfaceVisualizer); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/MoteTypeInformation.java b/tools/cooja/java/se/sics/cooja/plugins/MoteTypeInformation.java new file mode 100644 index 000000000..160e59e4e --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/MoteTypeInformation.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: MoteTypeInformation.java,v 1.1 2006/08/21 12:13:08 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * Shows a summary of all created mote types. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Mote Type Information") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class MoteTypeInformation extends VisPlugin { + private static Logger logger = Logger.getLogger(MoteTypeInformation.class); + + private static final long serialVersionUID = 1L; + + private Simulation mySimulation; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + /** + * Create a new mote type information window. + * + * @param simulation Simulation + */ + public MoteTypeInformation(Simulation simulation) { + super("Mote Type Information *frozen*"); + + mySimulation = simulation; + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + + // Visualize mote types + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("-- MOTE TYPES AT TIME " + simulation.getSimulationTime() + " --"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.NORTH, label); + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,10))); + + for (MoteType moteType: mySimulation.getMoteTypes()) { + smallPane = new JPanel(); + smallPane.setLayout(new BorderLayout()); + + label = new JLabel(GUI.getDescriptionOf(moteType) +": " + + "ID=" + moteType.getIdentifier() + + ", \"" + moteType.getDescription() + "\""); + label.setAlignmentX(JLabel.CENTER_ALIGNMENT); + smallPane.add(BorderLayout.NORTH, label); + + JPanel moteTypeVisualizer = moteType.getTypeVisualizer(); + if (moteTypeVisualizer != null) { + moteTypeVisualizer.setBorder(BorderFactory.createEtchedBorder()); + smallPane.add(BorderLayout.CENTER, moteTypeVisualizer); + } else + smallPane.add(BorderLayout.CENTER, Box.createVerticalStrut(25)); + + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + } + + + this.setContentPane(new JScrollPane(mainPane, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + pack(); + setPreferredSize(new Dimension(350,500)); + setSize(new Dimension(350,500)); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/SimControl.java b/tools/cooja/java/se/sics/cooja/plugins/SimControl.java new file mode 100644 index 000000000..be670179e --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/SimControl.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: SimControl.java,v 1.1 2006/08/21 12:13:08 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.text.NumberFormat; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * The Control Panel is a simple control panel for simulations. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Control Panel") +@VisPluginType(VisPluginType.SIM_STANDARD_PLUGIN) +public class SimControl extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(SimControl.class); + + private static final int MIN_DELAY_TIME = 0; + private static final int MAX_DELAY_TIME = 100; + + private Simulation simulation; + + private JSlider sliderDelay; + private JLabel simulationTime; + private JButton startButton, stopButton; + private JFormattedTextField stopTimeTextField; + private int simulationStopTime = -1; + + private Observer simObserver; + private Observer tickObserver; + + private long lastTextUpdateTime = -1; + + /** + * Create a new simulation control panel. + * + * @param simulationToControl Simulation to control + */ + public SimControl(Simulation simulationToControl) { + super("Control Panel - " + simulationToControl.getTitle()); + + simulation = simulationToControl; + + JButton button; + JPanel smallPanel; + + // Register as tickobserver + simulation.addTickObserver(tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + // During simulation running, only update text 10 times each second + if (lastTextUpdateTime < System.currentTimeMillis() - 100) { + lastTextUpdateTime = System.currentTimeMillis(); + simulationTime.setText("Current simulation time: " + simulation.getSimulationTime()); + } + + if (simulationStopTime > 0 && simulationStopTime <= simulation.getSimulationTime() && simulation.isRunning()) { + // Time to stop simulation now + simulation.stopSimulation(); + simulationStopTime = -1; + } + } + }); + + // Register as simulation observer + simulation.addObserver(simObserver = new Observer() { + public void update(Observable obs, Object obj) { + if (simulation.isRunning()) { + startButton.setEnabled(false); + stopButton.setEnabled(true); + } else { + startButton.setEnabled(true); + stopButton.setEnabled(false); + simulationStopTime = -1; + } + sliderDelay.setValue((int) simulation.getDelayTime()); + simulationTime.setText("Current simulation time: " + simulation.getSimulationTime()); + } + }); + + + // Main panel + JPanel controlPanel = new JPanel(); + controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); + + setContentPane(controlPanel); + + // Add control buttons + smallPanel = new JPanel(); + smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); + smallPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5)); + + button = new JButton("Start"); + button.setActionCommand("start"); + button.addActionListener(myEventHandler); + startButton = button; + smallPanel.add(button); + + button = new JButton("Stop"); + button.setActionCommand("stop"); + button.addActionListener(myEventHandler); + stopButton = button; + smallPanel.add(button); + + button = new JButton("Tick all motes once"); + button.setActionCommand("tickall"); + button.addActionListener(myEventHandler); + smallPanel.add(button); + + smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + controlPanel.add(smallPanel); + + smallPanel = new JPanel(); + smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.X_AXIS)); + smallPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 10, 5)); + + button = new JButton("Run until"); + button.setActionCommand("rununtil"); + button.addActionListener(myEventHandler); + smallPanel.add(button); + + smallPanel.add(Box.createHorizontalStrut(10)); + + NumberFormat integerFormat = NumberFormat.getIntegerInstance(); + stopTimeTextField = new JFormattedTextField(integerFormat); + stopTimeTextField.setValue(simulation.getSimulationTime()); + stopTimeTextField.addPropertyChangeListener("value", new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + JFormattedTextField numberTextField = (JFormattedTextField) e.getSource(); + int untilTime = ((Number) numberTextField.getValue()).intValue(); + if (untilTime < simulation.getSimulationTime()) { + numberTextField.setValue(new Integer(simulation.getSimulationTime() + simulation.getTickTime())); + } + } + }); + smallPanel.add(stopTimeTextField); + + smallPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + controlPanel.add(smallPanel); + + + // Add delay slider + smallPanel = new JPanel(); + smallPanel.setLayout(new BoxLayout(smallPanel, BoxLayout.Y_AXIS)); + smallPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + + simulationTime = new JLabel(); + simulationTime.setText("Current simulation time: " + simulation.getSimulationTime()); + + smallPanel.add(simulationTime); + smallPanel.add(Box.createRigidArea(new Dimension(0, 10))); + + smallPanel.add(new JLabel("Delay (ms) between each tick")); + + JSlider slider; + if (simulation.getDelayTime() > MAX_DELAY_TIME) + slider = new JSlider(JSlider.HORIZONTAL, MIN_DELAY_TIME, simulation.getDelayTime(), simulation.getDelayTime()); + else + slider = new JSlider(JSlider.HORIZONTAL, MIN_DELAY_TIME, MAX_DELAY_TIME, simulation.getDelayTime()); + + slider.addChangeListener(myEventHandler); + slider.setMajorTickSpacing(20); + slider.setPaintTicks(true); + + slider.setPaintLabels(true); + + sliderDelay = slider; + smallPanel.add(slider); + + controlPanel.add(smallPanel); + + pack(); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + private class MyEventHandler implements ActionListener, ChangeListener { + public void stateChanged(ChangeEvent e) { + if (e.getSource() == sliderDelay) { + simulation.setDelayTime(sliderDelay.getValue()); + } else + logger.debug("Unhandled state change: " + e); + } + public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("start")) { + simulationStopTime = -1; // Reset until time + simulation.startSimulation(); + } else if (e.getActionCommand().equals("stop")) { + simulationStopTime = -1; // Reset until time + if (simulation.isRunning()) + simulation.stopSimulation(); + } else if (e.getActionCommand().equals("tickall")) { + simulationStopTime = -1; // Reset until time + simulation.tickSimulation(); + } else if (e.getActionCommand().equals("rununtil")) { + // Set new stop time + simulationStopTime = ((Number) stopTimeTextField.getValue()).intValue(); + if (simulationStopTime > simulation.getSimulationTime() && !simulation.isRunning()) { + simulation.startSimulation(); + } else { + if (simulation.isRunning()) + simulation.stopSimulation(); + simulationStopTime = -1; + } + } else + logger.debug("Unhandled action: " + e.getActionCommand()); + } + } MyEventHandler myEventHandler = new MyEventHandler(); + + public void closePlugin() { + // Remove log observer from all log interfaces + if (simObserver != null) + simulation.deleteObserver(simObserver); + + if (tickObserver != null) + simulation.deleteTickObserver(tickObserver); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/SimInformation.java b/tools/cooja/java/se/sics/cooja/plugins/SimInformation.java new file mode 100644 index 000000000..fe35be0ca --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/SimInformation.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: SimInformation.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.util.*; +import javax.swing.*; + +import se.sics.cooja.*; + +/** + * SimInformation is a simple information window for simulations. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Simulation Information") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class SimInformation extends VisPlugin { + private static final long serialVersionUID = 1L; + private Simulation simulation; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private JLabel labelStatus; + private JLabel labelSimTime; + private JLabel labelNrMotes; + private JLabel labelNrMoteTypes; + + private Observer simObserver; + private Observer tickObserver; + + /** + * Create a new simulation information window. + * + * @param simulationToView Simulation to view + */ + public SimInformation(Simulation simulationToView) { + super("Simulation Information"); + + simulation = simulationToView; + + // Register as simulation observer + simulation.addObserver(simObserver = new Observer() { + public void update(Observable obs, Object obj) { + if (simulation.isRunning()) { + labelStatus.setText("RUNNING"); + } else { + labelStatus.setText("STOPPED"); + } + labelNrMotes.setText("" + simulation.getMotesCount()); + labelNrMoteTypes.setText("" + simulation.getMoteTypes().size()); + + } + }); + + // Register as tick observer + simulation.addTickObserver(tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + labelSimTime.setText("" + simulation.getSimulationTime()); + } + }); + + JLabel label; + JPanel mainPane = new JPanel(); + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + + // Status information + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Status"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(label); + + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + + label = new JLabel(); + if (simulation.isRunning()) + label.setText("RUNNING"); + else + label.setText("STOPPED"); + + labelStatus = label; + smallPane.add(label); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + // Current simulation time + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Simulation time"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(label); + + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + + label = new JLabel(); + label.setText("" + simulation.getSimulationTime()); + + labelSimTime = label; + smallPane.add(label); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + // Number of motes + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Number of motes"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(label); + + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + + label = new JLabel(); + label.setText("" + simulation.getMotesCount()); + + labelNrMotes = label; + smallPane.add(label); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + // Number of mote types + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Number of mote types"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(label); + + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + + label = new JLabel(); + label.setText("" + simulation.getMoteTypes().size()); + + labelNrMoteTypes = label; + smallPane.add(label); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + + // Radio Medium type + smallPane = new JPanel(); + smallPane.setAlignmentX(Component.LEFT_ALIGNMENT); + smallPane.setLayout(new BoxLayout(smallPane, BoxLayout.X_AXIS)); + label = new JLabel("Radio medium"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(label); + + smallPane.add(Box.createHorizontalStrut(10)); + smallPane.add(Box.createHorizontalGlue()); + + Class radioMediumClass = simulation.getRadioMedium().getClass(); + String description = GUI.getDescriptionOf(radioMediumClass); + label = new JLabel(description); + + smallPane.add(label); + + mainPane.add(smallPane); + + mainPane.add(Box.createRigidArea(new Dimension(0,5))); + + + this.setContentPane(mainPane); + pack(); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + public void closePlugin() { + // Remove log observer from all log interfaces + if (simObserver != null) + simulation.deleteObserver(simObserver); + + if (tickObserver != null) + simulation.deleteTickObserver(tickObserver); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/VariableWatcher.java b/tools/cooja/java/se/sics/cooja/plugins/VariableWatcher.java new file mode 100644 index 000000000..5d1c78f2b --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/VariableWatcher.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VariableWatcher.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.awt.event.*; +import java.beans.*; +import java.text.NumberFormat; +import javax.swing.*; + +import se.sics.cooja.*; + +/** + * Variable Watcher enables a user to watch mote variables during a simulation. + * Variables can be read or written either as bytes, integers (4 bytes) or byte arrays. + * + * User can also see which variables seems to be available on the selected node. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Variable Watcher") +@VisPluginType(VisPluginType.MOTE_PLUGIN) +public class VariableWatcher extends VisPlugin { + private static final long serialVersionUID = 1L; + + private SectionMoteMemory moteMemory; + + private final static int LABEL_WIDTH = 170; + private final static int LABEL_HEIGHT = 15; + + private final static int BYTE_INDEX = 0; + private final static int INT_INDEX = 1; + private final static int ARRAY_INDEX = 2; + + private JPanel lengthPane; + private JPanel valuePane; + private JComboBox varName; + private JFormattedTextField[] varValues; + private JFormattedTextField varLength; + private JButton writeButton; + + private NumberFormat integerFormat; + + /** + * Create a variable watcher window. + * + * @param moteToView Mote to view + */ + public VariableWatcher(Mote moteToView) { + super("Variable Watcher (" + moteToView + ")"); + + moteMemory = (SectionMoteMemory) moteToView.getMemory(); + + JLabel label; + integerFormat = NumberFormat.getIntegerInstance(); + JPanel mainPane = new JPanel(); + mainPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS)); + JPanel smallPane; + + // Variable name + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Variable name"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.WEST, label); + + varName = new JComboBox(); + varName.setEditable(true); + varName.setSelectedItem("[enter or pick name]"); + + String[] allPotentialVarNames = moteMemory.getVariableNames(); + for (String aVarName: allPotentialVarNames) + varName.addItem(aVarName); + + varName.addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent e) { + writeButton.setEnabled(false); + } + public void keyTyped(KeyEvent e) { + writeButton.setEnabled(false); + } + public void keyReleased(KeyEvent e) { + writeButton.setEnabled(false); + } + }); + + smallPane.add(BorderLayout.EAST, varName); + mainPane.add(smallPane); + + // Variable type + smallPane = new JPanel(new BorderLayout()); + label = new JLabel("Variable type"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + smallPane.add(BorderLayout.WEST, label); + + final JComboBox varType = new JComboBox(); + varType.addItem("Byte (1 byte)"); // BYTE_INDEX = 0 + varType.addItem("Integer (4 bytes)"); // INT_INDEX = 1 + varType.addItem("Byte array (x bytes)"); // ARRAY_INDEX = 2 + + varType.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (varType.getSelectedIndex() == ARRAY_INDEX) { + lengthPane.setVisible(true); + setNumberOfValues(((Number) varLength.getValue()).intValue()); + } else { + lengthPane.setVisible(false); + setNumberOfValues(1); + } + pack(); + } + }); + + smallPane.add(BorderLayout.EAST, varType); + mainPane.add(smallPane); + + // Variable length + lengthPane = new JPanel(new BorderLayout()); + label = new JLabel("Variable length"); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + lengthPane.add(BorderLayout.WEST, label); + + varLength = new JFormattedTextField(integerFormat); + varLength.setValue(new Integer(1)); + varLength.setColumns(4); + varLength.addPropertyChangeListener("value", new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent e) { + setNumberOfValues(((Number) varLength.getValue()).intValue()); + } + }); + + lengthPane.add(BorderLayout.EAST, varLength); + mainPane.add(lengthPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + + lengthPane.setVisible(false); + + // Variable value label + label = new JLabel("Variable value"); + label.setAlignmentX(JLabel.CENTER_ALIGNMENT); + label.setPreferredSize(new Dimension(LABEL_WIDTH,LABEL_HEIGHT)); + mainPane.add(label); + + // Variable value(s) + valuePane = new JPanel(); + valuePane.setLayout(new BoxLayout(valuePane, BoxLayout.X_AXIS)); + + varValues = new JFormattedTextField[1]; + varValues[0] = new JFormattedTextField(integerFormat); + varValues[0].setValue(new Integer(0)); + varValues[0].setColumns(3); + varValues[0].setText("?"); + + for (JFormattedTextField varValue: varValues) { + valuePane.add(varValue); + + } + + mainPane.add(valuePane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + + // Read/write buttons + smallPane = new JPanel(new BorderLayout()); + JButton button = new JButton("Read"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (varType.getSelectedIndex() == BYTE_INDEX) { + try { + byte val = moteMemory.getByteValueOf((String) varName.getSelectedItem()); + varValues[0].setValue(new Integer(val)); + varName.setBackground(Color.WHITE); + writeButton.setEnabled(true); + } catch (Exception ex) { + varName.setBackground(Color.RED); + writeButton.setEnabled(false); + } + } else if (varType.getSelectedIndex() == INT_INDEX) { + try { + int val = moteMemory.getIntValueOf((String) varName.getSelectedItem()); + varValues[0].setValue(new Integer(val)); + varName.setBackground(Color.WHITE); + writeButton.setEnabled(true); + } catch (Exception ex) { + varName.setBackground(Color.RED); + writeButton.setEnabled(false); + } + } else if (varType.getSelectedIndex() == ARRAY_INDEX) { + try { + int length = ((Number) varLength.getValue()).intValue(); + byte[] vals = moteMemory.getByteArray((String) varName.getSelectedItem(), length); + for (int i=0; i < length; i++) + varValues[i].setValue(new Integer(vals[i])); + varName.setBackground(Color.WHITE); + writeButton.setEnabled(true); + } catch (Exception ex) { + varName.setBackground(Color.RED); + writeButton.setEnabled(false); + } + } + } + }); + smallPane.add(BorderLayout.WEST, button); + + button = new JButton("Write"); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (varType.getSelectedIndex() == BYTE_INDEX) { + try { + byte val = (byte) ((Number) varValues[0].getValue()).intValue(); + moteMemory.setByteValueOf((String) varName.getSelectedItem(), val); + varName.setBackground(Color.WHITE); + } catch (Exception ex) { + varName.setBackground(Color.RED); + } + } else if (varType.getSelectedIndex() == INT_INDEX) { + try { + int val = ((Number) varValues[0].getValue()).intValue(); + moteMemory.setIntValueOf((String) varName.getSelectedItem(), val); + varName.setBackground(Color.WHITE); + } catch (Exception ex) { + varName.setBackground(Color.RED); + } + } else if (varType.getSelectedIndex() == ARRAY_INDEX) { + try { + int length = ((Number) varLength.getValue()).intValue(); + byte[] vals = new byte[length]; + for (int i=0; i < length; i++) { + vals[i] = (byte) ((Number) varValues[i].getValue()).intValue(); + } + + moteMemory.setByteArray((String) varName.getSelectedItem(), vals); + varName.setBackground(Color.WHITE); + writeButton.setEnabled(true); + } catch (Exception ex) { + varName.setBackground(Color.RED); + writeButton.setEnabled(false); + } + } + } + }); + smallPane.add(BorderLayout.EAST, button); + button.setEnabled(false); + writeButton = button; + + + mainPane.add(smallPane); + mainPane.add(Box.createRigidArea(new Dimension(0,25))); + + this.setContentPane(new JScrollPane(mainPane, + JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, + JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED)); + pack(); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + + } + + private void setNumberOfValues(int nr) { + valuePane.removeAll(); + + if (nr > 0) { + varValues = new JFormattedTextField[nr]; + for (int i=0; i < nr; i++) { + varValues[i] = new JFormattedTextField(integerFormat); + varValues[i] .setValue(new Integer(0)); + varValues[i] .setColumns(3); + varValues[i] .setText("?"); + valuePane.add(varValues[i]); + } + } + pack(); + } + + public void closePlugin() { + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/VisBattery.java b/tools/cooja/java/se/sics/cooja/plugins/VisBattery.java new file mode 100644 index 000000000..221c29a25 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/VisBattery.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisBattery.java,v 1.1 2006/08/21 12:13:08 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.util.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.Battery; + +/** + * A Battery Visualizer indicates mote energy levels by painting them in + * different colors. The mote is painted in a grayscale where white is max + * energy and black is no energy left. If a mote has no battery interface or + * infinite energy, it is painted blue. If a mote is dead it is painted red. + * + * A VisBattery observers both the simulation and all mote batteries. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Battery Visualizer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class VisBattery extends Visualizer2D { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(VisBattery.class); + + private Simulation simulation; + + private Observer simObserver = null; // Watches simulation changes + private Observer batteryObserver = null; // Watches mote battery changes + + /** + * Creates a new battery visualizer. + * + * @param simulationToVisualize + * Simulation to visualize + */ + public VisBattery(Simulation simulationToVisualize) { + super(simulationToVisualize); + setTitle("Battery Visualizer"); + + simulation = simulationToVisualize; + + // Always observe all motes in simulation + batteryObserver = new Observer() { + public void update(Observable obs, Object obj) { + getCurrentCanvas().repaint(); + } + }; + simulation.addObserver(simObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Register (or reregister) as observer on all motes + for (int i = 0; i < simulation.getMotesCount(); i++) { + Battery battery = simulation.getMote(i).getInterfaces().getBattery(); + if (battery != null) { + battery.addObserver(batteryObserver); + } + } + } + }); + simObserver.update(null, null); + + } + + public void postVisualizeSimulation(Graphics g) { + } + + public Color[] getColorOf(Mote mote) { + if (mote.getState() == Mote.STATE_DEAD) + return new Color[]{Color.RED}; + + Battery battery = mote.getInterfaces().getBattery(); + if (battery == null) { + return new Color[]{Color.BLUE}; + } + + if (battery.hasInfiniteEnergy()) { + return new Color[]{Color.BLUE}; + } + + double currentEnergy = battery.getCurrentEnergy(); + + if (currentEnergy < 0.0) { + return new Color[]{Color.RED}; + } + + int grayValue = (int) (255 * (currentEnergy / battery.getInitialEnergy())); + return new Color[]{new Color(grayValue, grayValue, grayValue)}; + } + + public void closePlugin() { + if (simObserver != null) { + simulation.deleteObserver(simObserver); + + // Delete all state observers + for (int i = 0; i < simulation.getMotesCount(); i++) { + Battery battery = simulation.getMote(i).getInterfaces().getBattery(); + if (battery != null) { + battery.deleteObserver(batteryObserver); + } + } + } + + super.closePlugin(); + } + + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/VisState.java b/tools/cooja/java/se/sics/cooja/plugins/VisState.java new file mode 100644 index 000000000..ec3bb33e7 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/VisState.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisState.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.util.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; + +/** + * A State Visualizer indicates mote states by painting them in different colors. + * Active motes are green, sleeping motes are gray and dead motes are read. + * + * The inner color indicates the mote type. + * + * A VisState observes both the simulation and all mote states. + * + * @author Fredrik Osterlind + */ +@ClassDescription("State Visualizer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class VisState extends Visualizer2D { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(VisState.class); + + private Simulation simulation; + + private static final Color moteTypeColors[] = new Color[] { + Color.MAGENTA, + Color.CYAN, + Color.ORANGE, + Color.GREEN, + Color.BLUE, + Color.RED, + Color.YELLOW, + }; + + private Observer simObserver = null; // Watches simulation changes + private Observer stateObserver = null; // Watches mote state changes + + /** + * Creates a new state visualizer. + * + * @param simulationToVisualize Simulation to visualize + */ + public VisState(Simulation simulationToVisualize) { + super(simulationToVisualize); + setTitle("State Visualizer"); + + simulation = simulationToVisualize; + + // Always observe all motes in simulation + stateObserver = new Observer() { + public void update(Observable obs, Object obj) { + getCurrentCanvas().repaint(); + } + }; + simulation.addObserver(simObserver = new Observer() { + public void update(Observable obs, Object obj) { + // Register (or reregister) as observer on all motes + for (int i=0; i < simulation.getMotesCount(); i++) { + Mote mote = simulation.getMote(i); + if (mote != null) { + mote.addStateObserver(stateObserver); + } + } + } + }); + simObserver.update(null, null); + + } + + public void postVisualizeSimulation(Graphics g) { + } + + public Color[] getColorOf(Mote mote) { + Color[] returnColors = new Color[2]; + + // If mote is sleeping, make outer circle blue + if (mote.getState() == Mote.STATE_LPM) + returnColors[1] = Color.GRAY; + + // If mote is dead, make outer circle red + else if (mote.getState() == Mote.STATE_DEAD) + returnColors[1] = Color.RED; + + else + returnColors[1] = Color.GREEN; // make outer circle green + + + // Associate different colors with different mote types + Vector allTypes = simulation.getMoteTypes(); + int numberOfTypes = allTypes.size(); + + for (int colCounter=0; colCounter < numberOfTypes && colCounter < moteTypeColors.length; colCounter++) { + if (mote.getType() == allTypes.get(colCounter)) { + returnColors[0] = moteTypeColors[colCounter]; + return returnColors; + } + } + + returnColors[0] = Color.WHITE; + return returnColors; + } + + public void closePlugin() { + if (simObserver != null) { + simulation.deleteObserver(simObserver); + + // Delete all state observers + for (int i=0; i < simulation.getMotesCount(); i++) { + Mote mote = simulation.getMote(i); + if (mote != null) { + mote.deleteStateObserver(stateObserver); + } + } + } + + super.closePlugin(); + } +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/VisTraffic.java b/tools/cooja/java/se/sics/cooja/plugins/VisTraffic.java new file mode 100644 index 000000000..319f0fc65 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/VisTraffic.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: VisTraffic.java,v 1.1 2006/08/21 12:13:07 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Observable; +import java.util.Observer; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.Position; + +/** + * A Traffic Visualizer visualizes radio traffic by painting lines between + * communicating motes. + * + * A VisTraffic observers the current simulation radio medium. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Traffic Visualizer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public class VisTraffic extends Visualizer2D { + private static final long serialVersionUID = 1L; + private RadioMedium radioMedium; + + /** + * The image painted on screen at repaint(). + */ + public BufferedImage image; + + private Position oldSmallPosition = null; + private Position oldBigPosition = null; + private Simulation simulation; + private int oldNrMotes; + + private Observer radioObserver = null; + + /** + * Creates a new VisTraffic visualizer. + * + * @param simulationToVisualize + * Simulation to visualize + */ + public VisTraffic(Simulation simulationToVisualize) { + super(simulationToVisualize); + setTitle("Traffic Visualizer"); + simulation = simulationToVisualize; + oldNrMotes = simulation.getMotesCount(); + + radioMedium = simulationToVisualize.getRadioMedium(); + + // Repaint when radio medium has sent data + simulationToVisualize.getRadioMedium().addRadioMediumObserver(radioObserver = new Observer() { + public void update(Observable obs, Object obj) { + Graphics2D g2d = image.createGraphics(); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f)); + if (radioMedium != null && radioMedium.getLastTickConnections() != null) { + for (RadioConnection conn: radioMedium.getLastTickConnections()) { + paintConnection(conn, g2d); + } + } + getCurrentCanvas().repaint(); + } + }); + + } + + /** + * Paints given connection on given graphics. + * + * @param connection Connection + * @param g2d Graphics to paint on + */ + protected void paintConnection(RadioConnection connection, Graphics g2d) { + Point sourcePixelPosition = transformPositionToPixel(connection.getSourcePosition()); + for (Position destPosition: connection.getDestinationPositons()) { + Point destPixelPosition = transformPositionToPixel(destPosition); + g2d.setColor(getColorOf(connection)); + g2d.drawLine(sourcePixelPosition.x, sourcePixelPosition.y, + destPixelPosition.x, destPixelPosition.y); + + } + } + + /** + * Returns color a specific connection should be painted in. + * + * @param connection Connection + */ + protected Color getColorOf(RadioConnection connection) { + return Color.BLACK; + } + + protected void calculateTransformations() { + super.calculateTransformations(); + Dimension imageDimension = getCurrentCanvas().getPreferredSize(); + if (imageDimension.height <= 0 || imageDimension.width <= 0) + return; + + // Recreate image if .. + if ( + // .. it hasn't been painted before + oldSmallPosition == null || + oldBigPosition == null || + image == null || + simulation == null || + + // .. mote changes may have changed the transformation. + simulation.getMotesCount() != oldNrMotes || + + // .. visualization window has changed the transformation. + imageDimension.height != image.getHeight() || + imageDimension.width != image.getWidth() + ) { + if (simulation != null) + oldNrMotes = simulation.getMotesCount(); + + BufferedImage oldImage = image; + image = new BufferedImage(imageDimension.width, imageDimension.height, BufferedImage.TYPE_4BYTE_ABGR); + image.createGraphics().setColor(Color.WHITE); + image.createGraphics().fillRect(0, 0, imageDimension.width, imageDimension.height); + + // If old data exists, keep it + if (oldSmallPosition != null && oldBigPosition != null) { + Point oldSmallPixelPos = transformPositionToPixel(oldSmallPosition); + Point oldBigPixelPos = transformPositionToPixel(oldBigPosition); + image.createGraphics().drawImage(oldImage, + oldSmallPixelPos.x, + oldSmallPixelPos.y, + oldBigPixelPos.x-oldSmallPixelPos.x, + oldBigPixelPos.y-oldSmallPixelPos.y, + null); + } + + oldSmallPosition = transformPixelToPositon(new Point(0,0)); + oldBigPosition = transformPixelToPositon(new Point(imageDimension.width, imageDimension.height)); + } + } + + public void closePlugin() { + super.closePlugin(); + + // Remove radio observer + radioMedium.deleteRadioMediumObserver(radioObserver); + } + + public Color[] getColorOf(Mote m) { + return null; + } + + protected void visualizeSimulation(Graphics g) { + g.drawImage(image, 0, 0, null); + } + +} diff --git a/tools/cooja/java/se/sics/cooja/plugins/Visualizer2D.java b/tools/cooja/java/se/sics/cooja/plugins/Visualizer2D.java new file mode 100644 index 000000000..1b3d6f664 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/plugins/Visualizer2D.java @@ -0,0 +1,539 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: Visualizer2D.java,v 1.1 2006/08/21 12:13:08 fros4943 Exp $ + */ + +package se.sics.cooja.plugins; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; + +/** + * Visualizer2D is an abstract mote visualizer for simulations. All motes are + * painted in the XY-plane, as seen from positive Z axis. + * + * An implementation of this class must colorize the different motes, each mote + * has two different colors; inner and outer. + * + * By right-clicking the mouse on a mote a popup menu will be displayed. From + * this menu mote plugins can be started. or the mote can be moved. Each + * implementation may also register its own actions to be accessed from this + * menu. + * + * A Visualizer2D observers both the simulation and all mote positions. + * + * @author Fredrik Osterlind + */ +@ClassDescription("2D Mote Visualizer") +@VisPluginType(VisPluginType.SIM_PLUGIN) +public abstract class Visualizer2D extends VisPlugin { + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(Visualizer2D.class); + + private double factorXCoordToPixel; + private double factorYCoordToPixel; + private double smallestXCoord; + private double smallestYCoord; + + private Simulation simulation = null; + private final JPanel canvas; + private Visualizer2D myPlugin; + + private static final int CANVAS_BORDER_WIDTH = 25; + private static final int MOTE_RADIUS = 8; + + private boolean moteIsBeingMoved = false; + private Mote moteToMove = null; + + private Observer simObserver = null; // Watches simulation changes + private Observer posObserver = null; // Watches position changes + + public interface MoteMenuAction { + public boolean isEnabled(Mote mote); + public String getDescription(Mote mote); + public void doAction(Mote mote); + } + + private class MoveMoteMenuAction implements MoteMenuAction { + public boolean isEnabled(Mote mote) { + return true; + } + public String getDescription(Mote mote) { + return "Move " + mote; + } + public void doAction(Mote mote) { + beginMoveRequest(mote); + } + }; + + private Vector menuActions = new Vector(); + + /** + * Registers as an simulation observer and initializes the canvas. + * + * @param simulationToVisualize + * Simulation to visualize + */ + public Visualizer2D(Simulation simulationToVisualize) { + super("Visualizer2D"); + + myPlugin = this; + + // Set initial bounds of frame + this.setBounds(150, 150, 300, 300); + setVisible(true); + + simulation = simulationToVisualize; + + // Create "canvas" to paint on + canvas = new JPanel() { + private static final long serialVersionUID = 1L; + public void paintComponent(Graphics g) { + super.paintComponent(g); + visualizeSimulation(g); + } + }; + canvas.setPreferredSize(new Dimension(getSize().width - 16, + getSize().height - 38)); + canvas.setBorder(BorderFactory.createLineBorder(Color.GREEN, 2)); + canvas.setBackground(Color.WHITE); + calculateTransformations(); + + this.setContentPane(canvas); + + // Detect general simulation changes + posObserver = new Observer() { + public void update(Observable obs, Object obj) { + calculateTransformations(); + canvas.repaint(); + } + }; + simulation.addObserver(simObserver = new Observer() { + public void update(Observable obs, Object obj) { + canvas.setPreferredSize(new Dimension(getSize().width - 16, + getSize().height - 38)); + + // Register (or reregister) as observer on all mote positions + for (int i = 0; i < simulation.getMotesCount(); i++) { + Position posIntf = simulation.getMote(i).getInterfaces() + .getPosition(); + if (posIntf != null) { + posIntf.addObserver(posObserver); + } + } + + calculateTransformations(); + canvas.repaint(); + } + }); + simObserver.update(null, null); + + canvas.addMouseMotionListener(new MouseMotionListener() { + public void mouseMoved(MouseEvent e) { + myPlugin.handleMoveRequest(e.getPoint().x, e.getPoint().y, false); + } + public void mouseDragged(MouseEvent e) { + myPlugin.handleMoveRequest(e.getPoint().x, e.getPoint().y, false); + } + }); + + // Detect mouse events + canvas.addMouseListener(new MouseListener() { + public void mousePressed(MouseEvent e) { + if (e.isPopupTrigger()) + myPlugin.handlePopupRequest(e.getPoint().x, e.getPoint().y); + else { + myPlugin.handleMoveRequest(e.getPoint().x, e.getPoint().y, false); + } + } + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) + myPlugin.handlePopupRequest(e.getPoint().x, e.getPoint().y); + else { + myPlugin.handleMoveRequest(e.getPoint().x, e.getPoint().y, true); + } + } + public void mouseEntered(MouseEvent e) { + if (e.isPopupTrigger()) + myPlugin.handlePopupRequest(e.getPoint().x, e.getPoint().y); + } + public void mouseExited(MouseEvent e) { + if (e.isPopupTrigger()) + myPlugin.handlePopupRequest(e.getPoint().x, e.getPoint().y); + } + public void mouseClicked(MouseEvent e) { + if (e.isPopupTrigger()) + myPlugin.handlePopupRequest(e.getPoint().x, e.getPoint().y); + } + }); + + // Detect component events + addComponentListener(new ComponentListener() { + public void componentMoved(ComponentEvent ce) { + // NOP + } + public void componentShown(ComponentEvent ce) { + // NOP + } + public void componentHidden(ComponentEvent ce) { + // NOP + } + public void componentResized(ComponentEvent ce) { + canvas.setPreferredSize(new Dimension(getSize().width - 16, + getSize().height - 38)); + calculateTransformations(); + canvas.repaint(); + } + }); + + // Add menu action for moving motes + addMoteMenuAction(new MoveMoteMenuAction()); + + try { + setSelected(true); + } catch (java.beans.PropertyVetoException e) { + // Could not select + } + } + + /** + * Add new mote menu action. + * + * @see MoteMenuAction + * @param menuAction Menu action + */ + public void addMoteMenuAction(MoteMenuAction menuAction) { + menuActions.add(menuAction); + } + + private void handlePopupRequest(final int x, final int y) { + final Vector foundMotes = findMotesAtPosition(x, y); + if (foundMotes == null || foundMotes.size() == 0) + return; + + JPopupMenu pickMoteMenu = new JPopupMenu(); + pickMoteMenu.add(new JLabel("Select action:")); + pickMoteMenu.add(new JSeparator()); + + // Add 'show mote plugins'-actions + for (final Mote mote : foundMotes) { + final Point pos = new Point(canvas.getLocationOnScreen().x + x, canvas + .getLocationOnScreen().y + + y); + + JMenuItem menuItem = new JMenuItem("Open mote plugin for " + mote); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + GUI.currentGUI.showMotePluginsMenu(canvas, mote, pos); + } + }); + pickMoteMenu.add(menuItem); + } + + // Add the rest of the actions + for (final MoteMenuAction menuAction : menuActions) { + for (final Mote mote : foundMotes) { + if (menuAction.isEnabled(mote)) { + JMenuItem menuItem = new JMenuItem(menuAction.getDescription(mote)); + menuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + menuAction.doAction(mote); + } + }); + pickMoteMenu.add(menuItem); + } + } + } + + // Show menu + Point pos = new Point(canvas.getLocationOnScreen().x + x, canvas + .getLocationOnScreen().y + + y); + pickMoteMenu.setLocation(pos.x, pos.y); + pickMoteMenu.setInvoker(canvas); + pickMoteMenu.setVisible(true); + } + + private void beginMoveRequest(Mote moteToMove) { + moteIsBeingMoved = true; + this.moteToMove = moteToMove; + } + + private void handleMoveRequest(final int x, final int y, + boolean wasJustReleased) { + if (!moteIsBeingMoved) { + return; + } + + if (!wasJustReleased) { + visualizeSimulation(canvas.getGraphics()); + canvas.paintImmediately(x - 40, y - 40, 80, 80); + canvas.getGraphics().setColor(Color.GRAY); + canvas.getGraphics().drawOval(x - MOTE_RADIUS, y - MOTE_RADIUS, + 2 * MOTE_RADIUS, 2 * MOTE_RADIUS); + } + + if (wasJustReleased) { + moteIsBeingMoved = false; + + Position newXYValues = transformPixelToPositon(new Point(x, y)); + int returnValue = JOptionPane.showConfirmDialog(myPlugin, "Mote mote to" + + "\nX=" + newXYValues.getXCoordinate() + "\nY=" + + newXYValues.getYCoordinate() + "\nZ=" + + moteToMove.getInterfaces().getPosition().getZCoordinate()); + + if (returnValue == JOptionPane.OK_OPTION) { + moteToMove.getInterfaces().getPosition().setCoordinates( + newXYValues.getXCoordinate(), newXYValues.getYCoordinate(), + moteToMove.getInterfaces().getPosition().getZCoordinate()); + repaint(); + } + } + + } + + /** + * Returns all motes at given position. + * + * @param clickedX + * X coordinate + * @param clickedY + * Y coordinate + * @return All motes at given position + */ + protected Vector findMotesAtPosition(int clickedX, int clickedY) { + double xCoord = factorXPixelToCoord(clickedX); + double yCoord = factorYPixelToCoord(clickedY); + + Vector motesFound = new Vector(); + + // Calculate painted mote radius in coordinates + double paintedMoteWidth = factorXPixelToCoord(MOTE_RADIUS) + - factorXPixelToCoord(0); + double paintedMoteHeight = factorYPixelToCoord(MOTE_RADIUS) + - factorYPixelToCoord(0); + + for (int i = 0; i < simulation.getMotesCount(); i++) { + Position pos = simulation.getMote(i).getInterfaces().getPosition(); + + // Transform to unit circle before checking if mouse hit this mote + double distanceX = Math.abs(xCoord - pos.getXCoordinate()) + / paintedMoteWidth; + double distanceY = Math.abs(yCoord - pos.getYCoordinate()) + / paintedMoteHeight; + + if (distanceX * distanceX + distanceY * distanceY <= 1) { + motesFound.add(simulation.getMote(i)); + } + } + if (motesFound.size() == 0) + return null; + + return motesFound; + } + + /** + * Get colors a certain mote should be painted with. May be overridden to get + * a different color scheme. + * + * Normally this method returns an array of two colors, one for the state + * (outer circle), the other for the type (inner circle). + * + * If this method only returns one color, the entire mote will be painted + * using that. + * + * @param mote + * Mote to paint + * @return Color[] { Inner color, Outer color } + */ + abstract public Color[] getColorOf(Mote mote); + + protected void visualizeSimulation(Graphics g) { + for (int i = 0; i < simulation.getMotesCount(); i++) { + Mote mote = simulation.getMote(i); + Color moteColors[] = getColorOf(mote); + Position motePos = mote.getInterfaces().getPosition(); + + Point pixelCoord = transformPositionToPixel(motePos); + int x = pixelCoord.x; + int y = pixelCoord.y; + + if (moteColors.length >= 1) { + g.setColor(moteColors[0]); + g.fillOval(x - MOTE_RADIUS, y - MOTE_RADIUS, 2 * MOTE_RADIUS, + 2 * MOTE_RADIUS); + } + + if (moteColors.length >= 2) { + g.setColor(moteColors[1]); + g.fillOval(x - MOTE_RADIUS / 2, y - MOTE_RADIUS / 2, MOTE_RADIUS, + MOTE_RADIUS); + } + + g.setColor(Color.BLACK); + g.drawOval(x - MOTE_RADIUS, y - MOTE_RADIUS, 2 * MOTE_RADIUS, + 2 * MOTE_RADIUS); + + } + } + + /** + * Recalculate size of canvas and factors for transforming between real and + * pixel coordinates. This method is called every time this frame is resized + * or created. + */ + protected void calculateTransformations() { + if (simulation.getMotesCount() == 0) { + smallestXCoord = 0; + smallestYCoord = 0; + factorXCoordToPixel = 1; + factorYCoordToPixel = 1; + return; + } + + double biggestXCoord, biggestYCoord; + + Position motePos = simulation.getMote(0).getInterfaces().getPosition(); + smallestXCoord = biggestXCoord = motePos.getXCoordinate(); + smallestYCoord = biggestYCoord = motePos.getYCoordinate(); + + // Get extreme coordinates + for (int i = 0; i < simulation.getMotesCount(); i++) { + motePos = simulation.getMote(i).getInterfaces().getPosition(); + + if (motePos.getXCoordinate() < smallestXCoord) + smallestXCoord = motePos.getXCoordinate(); + + if (motePos.getXCoordinate() > biggestXCoord) + biggestXCoord = motePos.getXCoordinate(); + + if (motePos.getYCoordinate() < smallestYCoord) + smallestYCoord = motePos.getYCoordinate(); + + if (motePos.getYCoordinate() > biggestYCoord) + biggestYCoord = motePos.getYCoordinate(); + + } + + if ((biggestXCoord - smallestXCoord) == 0) { + factorXCoordToPixel = 1; + } else + factorXCoordToPixel = ((double) canvas.getPreferredSize().width - 2 * CANVAS_BORDER_WIDTH) + / (biggestXCoord - smallestXCoord); + + if ((biggestYCoord - smallestYCoord) == 0) { + factorYCoordToPixel = 1; + } else + factorYCoordToPixel = ((double) canvas.getPreferredSize().height - 2 * CANVAS_BORDER_WIDTH) + / (biggestYCoord - smallestYCoord); + } + + /** + * Transforms a real-world position to a pixel which can be painted onto the + * current sized canvas. + * + * @param pos + * Real-world position + * @return Pixel coordinates + */ + public Point transformPositionToPixel(Position pos) { + return new Point(factorXCoordToPixel(pos.getXCoordinate()), + factorYCoordToPixel(pos.getYCoordinate())); + } + + /** + * Transforms real-world coordinates to a pixel which can be painted onto the + * current sized canvas. + * + * @param x Real world X + * @param y Real world Y + * @param z Real world Z (ignored) + * @return Pixel coordinates + */ + public Point transformPositionToPixel(double x, double y, double z) { + return new Point(factorXCoordToPixel(x), factorYCoordToPixel(y)); + } + + /** + * Transforms a pixel coordinate to a real-world. Z-value will always be 0. + * + * @param pixelPos + * On-screen pixel coordinate + * @return Real world coordinate (z=0). + */ + public Position transformPixelToPositon(Point pixelPos) { + Position dummyPosition = new Position(null); + dummyPosition.setCoordinates(factorXPixelToCoord(pixelPos.x), + factorYPixelToCoord(pixelPos.y), 0.0); + return dummyPosition; + } + + /** + * @return The current canvas to paint on + */ + public JPanel getCurrentCanvas() { + return canvas; + } + + private int factorXCoordToPixel(double xCoordinate) { + return (int) ((xCoordinate - smallestXCoord) * factorXCoordToPixel + CANVAS_BORDER_WIDTH); + } + private int factorYCoordToPixel(double yCoordinate) { + return (int) ((yCoordinate - smallestYCoord) * factorYCoordToPixel) + + CANVAS_BORDER_WIDTH; + } + private double factorXPixelToCoord(int xPixel) { + return ((double) (xPixel - CANVAS_BORDER_WIDTH) / factorXCoordToPixel) + + smallestXCoord; + } + private double factorYPixelToCoord(int yPixel) { + return ((double) (yPixel - CANVAS_BORDER_WIDTH) / factorYCoordToPixel) + + smallestYCoord; + } + + public void closePlugin() { + if (simObserver != null) { + simulation.deleteObserver(simObserver); + + for (int i = 0; i < simulation.getMotesCount(); i++) { + Position posIntf = simulation.getMote(i).getInterfaces().getPosition(); + if (posIntf != null) { + posIntf.deleteObserver(posObserver); + } + } + } + } + +} diff --git a/tools/cooja/java/se/sics/cooja/positioners/EllipsePositioner.java b/tools/cooja/java/se/sics/cooja/positioners/EllipsePositioner.java new file mode 100644 index 000000000..8b7ebbaf4 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/positioners/EllipsePositioner.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: EllipsePositioner.java,v 1.1 2006/08/21 12:13:11 fros4943 Exp $ + */ + +package se.sics.cooja.positioners; +import se.sics.cooja.*; + +/** + * Generates positions in a ellipse in the XY plane. + * (z position will always be start value of interval) + * + * @author Fredrik Osterlind + */ +@ClassDescription("Ellipse positioning") +public class EllipsePositioner extends Positioner { + + private double xRadius, yRadius, xMiddle, yMiddle; + private double zValue, deltaAngle, currentAngle; + + /** + * Creates a circle positioner. + * @param totalNumberOfMotes Total number of motes + * @param startX X interval start + * @param endX X interval end + * @param startY Y interval start + * @param endY Y interval end + * @param startZ Z interval start + * @param endZ Z interval end + */ + public EllipsePositioner(int totalNumberOfMotes, + double startX, double endX, + double startY, double endY, + double startZ, double endZ) { + xRadius = (endX - startX) / 2.0; + yRadius = (endY - startY) / 2.0; + xMiddle = startX + xRadius; + yMiddle = startY + yRadius; + zValue = startZ; + deltaAngle = 2*Math.PI / (double) totalNumberOfMotes; + currentAngle = 0; + } + + public double[] getNextPosition() { + currentAngle += deltaAngle; + return new double[] { + xMiddle + xRadius * Math.cos(currentAngle), + yMiddle + yRadius * Math.sin(currentAngle), + zValue}; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/positioners/LinearPositioner.java b/tools/cooja/java/se/sics/cooja/positioners/LinearPositioner.java new file mode 100644 index 000000000..e8dc7de74 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/positioners/LinearPositioner.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: LinearPositioner.java,v 1.1 2006/08/21 12:13:11 fros4943 Exp $ + */ + +package se.sics.cooja.positioners; +import se.sics.cooja.*; + +/** + * Generates positions linearly distributed in a given interval. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Linear positioning") +public class LinearPositioner extends Positioner { + + private double startX, startY, startZ; + private int addedInX, addedInY, addedInZ; + private int numberInX, numberInY, numberInZ; + private double xInterval, yInterval, zInterval; + + /** + * Creates a linear positioner. + * + * @param totalNumberOfMotes Total number of motes + * @param startX X interval start + * @param endX X interval end + * @param startY Y interval start + * @param endY Y interval end + * @param startZ Z interval start + * @param endZ Z interval end + */ + public LinearPositioner(int totalNumberOfMotes, + double startX, double endX, + double startY, double endY, + double startZ, double endZ) { + + this.startX = startX; + this.startY = startY; + this.startZ = startZ; + + double widthX = endX - startX; + double widthY = endY - startY; + double widthZ = endZ - startZ; + + if (widthX == 0) { + widthX = -1; + } + if (widthY == 0) { + widthY = -1; + } + if (widthZ == 0) { + widthZ = -1; + } + + double totalSpace = Math.abs(widthX * widthY * widthZ); + double spaceOfEachMote = totalSpace / (double) totalNumberOfMotes; + + int noDimensions = 0; + if (widthX > 0) + noDimensions++; + if (widthY > 0) + noDimensions++; + if (widthZ > 0) + noDimensions++; + + double sideLengthOfEachMote = Math.pow(spaceOfEachMote, 1.0/(double) noDimensions); + + this.numberInX = 0; + this.numberInY = 0; + this.numberInZ = 0; + + if (widthX > 0) + numberInX = (int) (widthX / sideLengthOfEachMote); + + if (widthY > 0) + numberInY = (int) (widthY / sideLengthOfEachMote); + + if (widthZ > 0) + numberInZ = (int) (widthZ / sideLengthOfEachMote); + + this.xInterval = widthX / numberInX; + this.yInterval = widthY / numberInY; + this.zInterval = widthZ / numberInZ; + + if (numberInX == 0) + xInterval = 0.0; + + if (numberInY == 0) + yInterval = 0.0; + + if (numberInZ == 0) + zInterval = 0.0; + + this.addedInX = 0; + this.addedInY = 0; + this.addedInZ = 0; + } + + public double[] getNextPosition() { + double[] newPosition = new double[] { + startX + addedInX*xInterval, + startY + addedInY*yInterval, + startZ + addedInZ*zInterval + }; + + addedInZ++; + + if (addedInZ >= numberInZ) { + addedInY++; + addedInZ = 0; + } + + if (addedInY >= numberInY) { + addedInX++; + addedInY = 0; + } + + return newPosition; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/positioners/RandomPositioner.java b/tools/cooja/java/se/sics/cooja/positioners/RandomPositioner.java new file mode 100644 index 000000000..f40403e5e --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/positioners/RandomPositioner.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: RandomPositioner.java,v 1.1 2006/08/21 12:13:11 fros4943 Exp $ + */ + +package se.sics.cooja.positioners; +import se.sics.cooja.*; + +/** + * Generates positions randomly distributed in a given interval. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Random positioning") +public class RandomPositioner extends Positioner { + double startX, endX, startY, endY, startZ, endZ; + + /** + * Creates a random positioner. + * @param totalNumberOfMotes Total number of motes + * @param startX X interval start + * @param endX X interval end + * @param startY Y interval start + * @param endY Y interval end + * @param startZ Z interval start + * @param endZ Z interval end + */ + public RandomPositioner(int totalNumberOfMotes, + double startX, double endX, + double startY, double endY, + double startZ, double endZ) { + this.startX = startX; + this.endX = endX; + this.startY = startY; + this.endY = endY; + this.startZ = startZ; + this.endZ = endZ; + } + + public double[] getNextPosition() { + return new double[] { + startX + Math.random()*(endX - startX), + startY + Math.random()*(endY - startY), + startZ + Math.random()*(endZ - startZ) + }; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/radiomediums/SilentRadioMedium.java b/tools/cooja/java/se/sics/cooja/radiomediums/SilentRadioMedium.java new file mode 100644 index 000000000..a4b6f577d --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/radiomediums/SilentRadioMedium.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: SilentRadioMedium.java,v 1.1 2006/08/21 12:13:14 fros4943 Exp $ + */ + +package se.sics.cooja.radiomediums; +import java.util.Collection; +import java.util.Observer; + +import org.jdom.Element; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; + +/** + * Silent radio. No data is ever transferred through this medium. + * + * @author Fredrik Osterlind + */ +@ClassDescription("Silent Radio") +public class SilentRadioMedium extends RadioMedium { + + public void registerMote(Mote mote, Simulation sim) { + // Do nothing + } + + public void unregisterMote(Mote mote, Simulation sim) { + // Do nothing + } + + public void registerRadioInterface(Radio radio, Position position, Simulation sim) { + // Do nothing + } + + public void unregisterRadioInterface(Radio radio, Simulation sim) { + // Do nothing + } + + public void addRadioMediumObserver(Observer observer) { + // Do nothing + } + + public void deleteRadioMediumObserver(Observer observer) { + // Do nothing + } + + public RadioConnection[] getLastTickConnections() { + return null; + } + + public void setConnectionLogger(ConnectionLogger connection) { + // Do nothing + } + + public Collection getConfigXML() { + return null; + } + + public boolean setConfigXML(Collection configXML) { + return true; + } + +} diff --git a/tools/cooja/java/se/sics/cooja/radiomediums/StandardRadioMedium.java b/tools/cooja/java/se/sics/cooja/radiomediums/StandardRadioMedium.java new file mode 100644 index 000000000..3d142efb3 --- /dev/null +++ b/tools/cooja/java/se/sics/cooja/radiomediums/StandardRadioMedium.java @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2006, Swedish Institute of Computer Science. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: StandardRadioMedium.java,v 1.1 2006/08/21 12:13:13 fros4943 Exp $ + */ + +package se.sics.cooja.radiomediums; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.*; +import org.jdom.Element; +import org.apache.log4j.Logger; + +import se.sics.cooja.*; +import se.sics.cooja.interfaces.*; +import se.sics.cooja.plugins.Visualizer2D; + +/** + * Standard radio medium has two different range parameters; one for + * transmitting and one for interfering other transmissions. + * + * Radio data are analysed when all registered motes have ticked once (this + * medium is registered as a tick observer). For every mote in transmission + * range of a sending mote, current listen status is changed from hearing + * nothing to hearing packet, or hearing packet to hearing noise. This way, if a + * mote hears two packets during one tick loop, it will receive none (noise). If + * a mote is in the interference range, the current listen status will be set to + * hears noise immediately. + * + * This radio medium registers a dynamic visual plugin: Radio Medium - Standard. + * Via this plugin ranges can be viewed and changed. Active connection may also + * be viewed. + * + * + * @author Fredrik Osterlind + */ +@ClassDescription("Standard Radio") +public class StandardRadioMedium extends RadioMedium { + private static Logger logger = Logger.getLogger(StandardRadioMedium.class); + + private Vector registeredPositions = new Vector(); + private Vector registeredRadios = new Vector(); + + private Vector sendingPositions = new Vector(); + private Vector sendingRadios = new Vector(); + + private static RadioMedium myRadioMedium; + + /** + * Visualizes radio traffic in the current radio medium. Allows a user to + * change transmission ranges. + * + * Sending motes are blue, receiving motes are green and motes that hear noise + * are painted red. Motes without radios are painted gray, and the rest are + * white. + * + * @author Fredrik Osterlind + */ + @ClassDescription("Radio Medium - Standard") + @VisPluginType(VisPluginType.SIM_PLUGIN) + public static class RadioMediumSimPlugin extends Visualizer2D { + private Mote selectedMote = null; + + private JSpinner transmissionSpinner = null; + private JSpinner interferenceSpinner = null; + + private Observer radioMediumObserver; + + private class ChangeRangesMenuAction implements MoteMenuAction { + public boolean isEnabled(Mote mote) { + return true; + } + public String getDescription(Mote mote) { + return "Change transmission ranges"; + } + public void doAction(Mote mote) { + transmissionSpinner.setVisible(true); + interferenceSpinner.setVisible(true); + repaint(); + } + }; + + public RadioMediumSimPlugin(Simulation sim) { + super(sim); + setTitle("Radio Medium - Standard"); + + // Create spinners for changing ranges + SpinnerNumberModel transmissionModel = new SpinnerNumberModel(); + transmissionModel.setValue(new Double(TRANSMITTING_RANGE)); + transmissionModel.setStepSize(new Double(1.0)); // 1m + transmissionModel.setMinimum(new Double(0.0)); + + SpinnerNumberModel interferenceModel = new SpinnerNumberModel(); + interferenceModel.setValue(new Double(INTERFERENCE_RANGE)); + interferenceModel.setStepSize(new Double(1.0)); // 1m + interferenceModel.setMinimum(new Double(0.0)); + + transmissionSpinner = new JSpinner(transmissionModel); + interferenceSpinner = new JSpinner(interferenceModel); + + ((JSpinner.DefaultEditor) transmissionSpinner.getEditor()).getTextField() + .setColumns(5); + ((JSpinner.DefaultEditor) interferenceSpinner.getEditor()).getTextField() + .setColumns(5); + transmissionSpinner.setToolTipText("Transmitting range"); + interferenceSpinner.setToolTipText("Interference range"); + + transmissionSpinner.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + TRANSMITTING_RANGE = ((SpinnerNumberModel) transmissionSpinner + .getModel()).getNumber().doubleValue(); + repaint(); + } + }); + + interferenceSpinner.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + INTERFERENCE_RANGE = ((SpinnerNumberModel) interferenceSpinner + .getModel()).getNumber().doubleValue(); + repaint(); + } + }); + + getCurrentCanvas().add(transmissionSpinner); + getCurrentCanvas().add(interferenceSpinner); + transmissionSpinner.setVisible(false); + interferenceSpinner.setVisible(false); + + // Add mouse listener for selecting motes + getCurrentCanvas().addMouseListener(new MouseListener() { + public void mouseExited(MouseEvent e) { + // Do nothing + } + public void mouseEntered(MouseEvent e) { + // Do nothing + } + public void mouseReleased(MouseEvent e) { + // Do nothing + } + public void mousePressed(MouseEvent e) { + Vector clickedMotes = findMotesAtPosition(e.getX(), e.getY()); + if (clickedMotes == null || clickedMotes.size() == 0) { + selectedMote = null; + transmissionSpinner.setVisible(false); + interferenceSpinner.setVisible(false); + repaint(); + return; + } + + // Select one of the clicked motes + if (clickedMotes.contains(selectedMote)) { + int pos = clickedMotes.indexOf(selectedMote); + if (pos < clickedMotes.size() - 1) + selectedMote = clickedMotes.get(pos + 1); + else + selectedMote = clickedMotes.firstElement(); + } else { + selectedMote = clickedMotes.firstElement(); + } + repaint(); + } + public void mouseClicked(MouseEvent e) { + } + }); + + // Register change ranges action + addMoteMenuAction(new ChangeRangesMenuAction()); + + // Observe our own radio medium + myRadioMedium + .addRadioMediumObserver(radioMediumObserver = new Observer() { + public void update(Observable obs, Object obj) { + getCurrentCanvas().repaint(); + } + }); + + } + + public void closePlugin() { + super.closePlugin(); + + myRadioMedium.deleteRadioMediumObserver(radioMediumObserver); + } + + public Color[] getColorOf(Mote mote) { + Radio moteRadio = mote.getInterfaces().getRadio(); + if (moteRadio == null) + return new Color[]{Color.GRAY}; + + if (selectedMote != null && mote == selectedMote) + return new Color[]{Color.CYAN}; + + if (moteRadio.getSendState() == Radio.SENT_SOMETHING) + return new Color[]{Color.BLUE}; + + if (moteRadio.getListenState() == Radio.HEARS_PACKET) + return new Color[]{Color.GREEN}; + + if (moteRadio.getListenState() == Radio.HEARS_NOISE) + return new Color[]{Color.RED}; + + return new Color[]{Color.WHITE}; + } + + public void visualizeSimulation(Graphics g) { + + // Paint transmission+interference areas for selected mote (if any) + if (selectedMote != null) { + Position motePos = selectedMote.getInterfaces().getPosition(); + + Point pixelCoord = transformPositionToPixel(motePos); + int x = pixelCoord.x; + int y = pixelCoord.y; + + Point translatedZero = transformPositionToPixel(0.0, 0.0, 0.0); + Point translatedInterference = transformPositionToPixel( + INTERFERENCE_RANGE, INTERFERENCE_RANGE, 0.0); + Point translatedTransmission = transformPositionToPixel( + TRANSMITTING_RANGE, TRANSMITTING_RANGE, 0.0); + + translatedInterference.x = Math.abs(translatedInterference.x + - translatedZero.x); + translatedInterference.y = Math.abs(translatedInterference.y + - translatedZero.y); + translatedTransmission.x = Math.abs(translatedTransmission.x + - translatedZero.x); + translatedTransmission.y = Math.abs(translatedTransmission.y + - translatedZero.y); + + // Interference + g.setColor(Color.DARK_GRAY); + g.fillOval(x - translatedInterference.x, y - translatedInterference.y, + 2 * translatedInterference.x, 2 * translatedInterference.y); + + // Transmission + g.setColor(Color.GREEN); + g.fillOval(x - translatedTransmission.x, y - translatedTransmission.y, + 2 * translatedTransmission.x, 2 * translatedTransmission.y); + + } + + // Let parent paint motes + super.visualizeSimulation(g); + + // Paint last tick connections + if (myRadioMedium != null + && myRadioMedium.getLastTickConnections() != null) { + for (RadioConnection conn : myRadioMedium.getLastTickConnections()) { + if (conn != null) { + Point sourcePoint = transformPositionToPixel(conn + .getSourcePosition()); + + // Paint destinations + for (Position destPos : conn.getDestinationPositons()) { + Point destPoint = transformPositionToPixel(destPos); + + g.setColor(Color.BLACK); + g + .drawLine(sourcePoint.x, sourcePoint.y, destPoint.x, + destPoint.y); + + } + } + } + } + } + } + + public StandardRadioMedium() { + // Register this radio medium's plugins + GUI.currentGUI.registerTemporaryPlugin(RadioMediumSimPlugin.class); + myRadioMedium = this; + } + + private boolean isTickObserver = false; + + private static double TRANSMITTING_RANGE = 20; // 20m + private static double INTERFERENCE_RANGE = 40; // 40m + + private static boolean RECEIVE_MY_OWN_PACKETS = false; + private static boolean DETECT_MY_OWN_PACKETS = true; + + private class RadioMediumObservable extends Observable { + private void transferredData() { + setChanged(); + notifyObservers(); + } + } + private RadioMediumObservable radioMediumObservable = new RadioMediumObservable(); + + private RadioConnection[] lastTickConnections = null; + + private ConnectionLogger myLogger = null; + + private Observer radioDataObserver = new Observer() { + public void update(Observable radio, Object obj) { + if (((Radio) radio).getSendState() == Radio.SENT_SOMETHING) { + if (!sendingPositions.contains((Position) obj)) { + sendingPositions.add((Position) obj); + sendingRadios.add((Radio) radio); + } + } + } + }; + + private Observer tickObserver = new Observer() { + public void update(Observable obs, Object obj) { + RadioConnection[] oldTickConnections = lastTickConnections; + lastTickConnections = null; + if (sendingPositions.size() > 0) { + final int numberSending = sendingPositions.size(); + lastTickConnections = new RadioConnection[numberSending]; + + // Loop through all sending radios + for (int sendNr = 0; sendNr < numberSending; sendNr++) { + Radio sendingRadio = sendingRadios.get(sendNr); + byte[] dataToSend = sendingRadio.getLastPacketSent(); + + lastTickConnections[sendNr] = new RadioConnection(); + lastTickConnections[sendNr].setSource(sendingRadios.get(sendNr), + sendingPositions.get(sendNr), dataToSend); + + // Set sending radio unable to receive any more data + if (RECEIVE_MY_OWN_PACKETS) { + sendingRadio.receivePacket(dataToSend); + sendingRadio.advanceListenState(); + } else if (DETECT_MY_OWN_PACKETS) { + sendingRadio.setListenState(Radio.HEARS_NOISE); + } + + // Loop through all radios that are listening + for (int listenNr = 0; listenNr < registeredPositions.size(); listenNr++) { + + // If not the sending radio.. + if (sendingRadios.get(sendNr) != registeredRadios.get(listenNr)) { + Radio listeningRadio = registeredRadios.get(listenNr); + + double distance = sendingPositions.get(sendNr).getDistanceTo( + registeredPositions.get(listenNr)); + + if (distance <= TRANSMITTING_RANGE) { + lastTickConnections[sendNr].addDestination(registeredRadios + .get(listenNr), registeredPositions.get(listenNr), + dataToSend); + + // If close enough to transmit ok.. + if (listeningRadio.getListenState() == Radio.HEARS_PACKET) { + // .. but listening radio already received a packet + listeningRadio.advanceListenState(); + } else if (listeningRadio.getListenState() == Radio.HEARS_NOISE) { + // .. but listening radio heard interference + listeningRadio.advanceListenState(); + } else { + // .. send packet + listeningRadio.receivePacket(dataToSend); + listeningRadio.setListenState(Radio.HEARS_PACKET); + } + } else if (distance <= INTERFERENCE_RANGE) { + // If close enough to sabotage other transmissions.. + if (listeningRadio.getListenState() == Radio.HEARS_PACKET) { + // .. and listening radio already received a packet + listeningRadio.advanceListenState(); + } else { + // .. and listening radio has done nothing sofar + listeningRadio.setListenState(Radio.HEARS_NOISE); + } + } + // else too far away + } + } + } + sendingPositions.clear(); + sendingRadios.clear(); + + if (myLogger != null) { + for (RadioConnection conn : lastTickConnections) + myLogger.logConnection(conn); + } + + } + if (lastTickConnections != oldTickConnections) + radioMediumObservable.transferredData(); + } + }; + + public void registerMote(Mote mote, Simulation sim) { + registerRadioInterface(mote.getInterfaces().getRadio(), mote + .getInterfaces().getPosition(), sim); + } + + public void unregisterMote(Mote mote, Simulation sim) { + unregisterRadioInterface(mote.getInterfaces().getRadio(), sim); + } + + public void registerRadioInterface(Radio radio, Position position, + Simulation sim) { + if (radio != null && position != null) { + if (!isTickObserver) { + sim.addTickObserver(tickObserver); + isTickObserver = true; + } + + registeredPositions.add(position); + registeredRadios.add(radio); + radio.addObserver(radioDataObserver); + } // else logger.warn("Standard Radio Medium not registering mote"); + } + + public void unregisterRadioInterface(Radio radio, Simulation sim) { + for (int i = 0; i < registeredRadios.size(); i++) { + if (registeredRadios.get(i).equals(radio)) { + registeredRadios.remove(i); + registeredPositions.remove(i); + radio.deleteObserver(radioDataObserver); + return; + } + } + logger.warn("Could not find radio: " + radio + " to unregister"); + } + + public void addRadioMediumObserver(Observer observer) { + radioMediumObservable.addObserver(observer); + } + + public void deleteRadioMediumObserver(Observer observer) { + radioMediumObservable.deleteObserver(observer); + } + + public RadioConnection[] getLastTickConnections() { + return lastTickConnections; + } + + public void setConnectionLogger(ConnectionLogger connection) { + myLogger = connection; + } + + public Collection getConfigXML() { + Vector config = new Vector(); + Element element; + + // Transmitting range + element = new Element("transmitting_range"); + element.setText(Double.toString(TRANSMITTING_RANGE)); + config.add(element); + + // Interference range + element = new Element("interference_range"); + element.setText(Double.toString(INTERFERENCE_RANGE)); + config.add(element); + + return config; + } + + public boolean setConfigXML(Collection configXML) { + for (Element element : configXML) { + if (element.getName().equals("transmitting_range")) { + TRANSMITTING_RANGE = Double.parseDouble(element.getText()); + } + + if (element.getName().equals("interference_range")) { + INTERFERENCE_RANGE = Double.parseDouble(element.getText()); + } + } + return true; + } + +} diff --git a/tools/cooja/lib/jdom.jar b/tools/cooja/lib/jdom.jar new file mode 100644 index 0000000000000000000000000000000000000000..c95de92066e7747625ab43b8fca48cf189afcb22 GIT binary patch literal 149522 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%0D(g5c5eCrX1A(jOMy3(|%LXjvJe zxm8elNbFct@H{VdZ*dc=p{(0|$YDkjH|_rLv2{JX;F;4x_$~RsV?nB5K^<#b}Jn)NVkUZJi?T z!|`ZB(_HNVy89Mz%;Zzz=8}kOqZ1>R{bL*(0sB>(9#EhdxK{FOTm%!`9t{YO=aXy8 zjx%wgl=cOm&ruRyd_Q-(hojZ<_Qi`|nfO8&&PUOiQJBXFO9^j6{Z;r@{?x^&pp@WY zxu1&(5UM%I?45QTCM9GXqYo$OtW0Vxfa_@1t;5k!D0MeW`X=3aAoZke>*FXVeU|;U z0OLyH*MD{K6x);T)GT~GySg0~H-G(-7HyGaTq^!uKRrj^93|5@us1&Ux+bhm*w}SF zJ}1nbGxzhN^&v^_%fsP7!@)rtY{9C_NNiLaRV(kqhO{mh37d|+Tj-u@fCU0f6HR-w zC|~+6rqTmH?WVIQX!YV^9$Hv^HAGud*`A@g;lmknS~^%rj#uopY~4RK?xu9jsD-iv z)XwFluifJCAf*89&9{Yn>=HLKJ7SAg+w69?Z~=OL-u&)0ssH4KYfZUf+xw!Bzp@L| z%2nsSWs1ac8qB^fGqOvzxY3l`P_c?NxTIN0A&nE!Bs2l~74ysA;5B=M!WYQy)@?nI z>GUWg7HafCn0Y4PA^#LQ%tno5sD2exsjY8bPpL1nZ7lsWvbBsP`XoOu)CvEMjC+!Z zZqCUReBuF>PEMOHKf`o8Qd?)hL=Bf89YIrA2O3TkUdcE~K{B(RA+=@*d4@rpSoh?f zhavc3I$rHo)Xo019b30RO`m|Z0XR7~Rc}}76}hOeQ2XVk4-bo*y10-ILB3AzlX(zG zZ~kcYbdU|=Knw9J1|+BbOxUXbd7*v$n;VH3xyTq1(zAVt$sJ2A?U~m=q^Ij?g3l~S zC<}?`n?Jfcpg{&#Y@skvEV-AyZVPEh-xR#R$Wao5fU#){>F{f9P%p2j^{g;fo7BE* z<^@|hh7bNT9`rMOZ8qs!G+J-`#4YuKMij>01xKO*Y9C@A$@nCx2Iaz%N|1E{h z_=o`(q+2zmK1r$6PLKtfsN4mSWA2+drSj^`J8v?jS4*6G7WEAwB=}-Vp<;lTO@|%= z10JNn<(-O_oSvZ=ewmBoV*x7KyqgPx)r=;vD6#r_S1rG9>ZGKTthkH-5$Nz}&w~`K za8&jz#_p0i6hY@+NdS|H>=ZxB?J1kwG!mm-H+Dpd-O2Ofy2IiPlppx!j%xs&Q{8HF zq;($1VtoR)DdIM5`5E!(i{$lk*%|n0_m2@N7=PRN?@yH&{mIYxpGPE$e_2O_94%~| z>}>!0@0qNsrG})2zAZ$HB*YI9F=B{A)eFTb+4xbj_^A1J)-jnV12^SZIuuE*)??WL6u!1=%* zuhvL<1e`bOT_JFYb}TLKEsBl6Hyog z-tiFEnHaF5VewyjI^Kl?qyAXYQ%2O!wwR`m3&2?0?j2jG5}YIuNBEWut8v z;PG7=r+jZnr3#N!J8f*08>wh;(vYB2+NmRirNWafYa4~2%>*l_KH0lyIKy@C~okYk)M4kaQ?6at? zEAgS}h!wV@QVgK;eis9z%+ur>cxlxgXBIq^r2jR&T+mG@RFwFXOME)s}uD5&7_3OzqQ_L79 zgi%kipeV^Ve)XdQ^p2qedPUR=U&i^8kKm}4hJ~XjciD5;@Q&^T^d`c!X=|OiX>m=u zy9zP18U?w<>4Dn=-!i!f)8{+j1*DvW&d6uS5pbVvOos4V$B&=Wv{>d!ZKYwcNX?|l zfyN6buRin%_i$CzF5eL!O-)k8w2<=5`F$v9*7T4R_t>a)D5)kkI<79JR&|)PFpqP? ziNiNfkwlawc$U(udrO7HP5HMYJ` zcQ9laGTk`FQ4B`X6a?<(c+aJM{9u{UxrJ22jM6y4DrE&ZTA^Rj0jE$}LJ1{mG!p$6 zsYca09jy4djHT&F-~?G#HBYM)Ez}{24d%BP+zx-*4MeY{Jq&FP$Lp(gDe*|_Q0vJW zX3=PjM3P)Bk$0>mzC0s8_zexAKAP-Y#LNO;(i{2v<^z|d=!TBKYOOul#&&OCV%`{* zTj*9zi}2W$FObJZGngJ>TO_H2+!)ehE1Ckw0gQWzfYneHw19O9G0$oW%gEX;cR766TrVfAbu|u*o_o2WevmB~QZV=qVl)+aV z(Z@X?*#nsrD&Y~ieVR6m2LRc^%nn@L+XHV;jNM=+_e?j@M~S%H(9C*T<+isKn0DjRb~868t%K7HR8@DQ0Z#S1 zodTgwG%E$?6Yd|CK!7(6wed%0*MRYIwA9u0E#yc zTZ%FB;D0rzJnC~SFE}ES(YARVPo9z4j4hl%+DqpXLCV#LU; zT6aN(quB;LtY+ly!C+$3ZD-i2ad=g@DSH;J1tCERwoJByW9EgZJ3vbLXa;FHR4Ro!ek03%Rrb#CeWr~<=sBbP@ z25UOD$q1%xG(xjKYmA~pkWc6{NhrnsS@-GDs=Keu%^;ePLI5VBE}^&cd@tQ^QSoWZ zM4}e~wu^oYMvRj!989Pz);}7Fj{|Euh*XXXT6vO``HsbAijQQhn9)I3ACDQFI#ss# zT7$w9NsKL0@62!*!$q2^wxEdUh>%iCGd`!FO!(KiBl0#%t{HIfm3&Jj(RvT#k;k!R z9!qpW-V|BB0k=g`a4mYnk_-YR$B1QTD;S%#wE?1lCwkNIWthVP@)YIdv0V39f2`$_ z_!`Yh>k0Z1Ip|(agE^>#`R5;ZV}Iq0EvqP3GkK7JW{oj6J~ysmf$#n?FOvIk$o? ztB%9zAXmM|NkJ7)l+}ljh%^Vr6*4&x*Oe<3U>o3M=s#B?@-F;6sv|H*y3+L%465sU z>LtWE-I)e7m4pWS<%N{W_TyIO5N$cJZEcZ54SRxE5d5kfsst(#g(ez;l?T4|-qLlm zjedEq5C4fuv?mOm4t8N({$?dCFZX?rgDyH0t3naTy)DtVcHiLlP6xk#Q_w)2*ps;S*nU9A?}K*m8}Toi z87&9pGSmuCWR&PXyTN|Oql^(0sk6(VtR_?w6~~rG zpnPSn`-t^XhaNp#qV%KNCOzOadz5~w9N>H>@}2%}cMv!GXkBu(czX=ILpQGwetSkA z;d1EvO5FWswz)k56h7Zkq|A-uD6a_McQD*hPT--p)Ld)7Ywgn-1V=hUP-~1uqX?&x z2g(ZB1$pQS;SR{VU(*I5u@5Z}8q0oGhCilA@MaZsr=(pAP;8>^(wwctz~yq}TXC|r zggDB3HPP6;VXbyAKDxOyo`vIN``loWsOba7)%?B+QY6W1;Q9J->07r^%aP zMcWp9+nVAQMw=<3S2^Vh&mD7M4$keH5DMq7SnV$K`KREOG1K-X^&@zJ{QnTV;n-(+`Icj1tEnrfB93Acess=1hnLd9{6aJ~KW)BCH4S5TOklICh z;%OTnGjDub8NkA0Y%CCW=$ABA8l)jA2B;|fCOsL=6nGr0As!>(A0T=Zb*Ouwub#X4 zaerU`+r6xovf;)I4kSDm>%puu)Uu1lser=tnYI+2Ibio#pi-Dcds%4r0-Hmi9bT$J zXlp!XX&-k2w&G2r4Q18Tx2mQToK% zXH`78=UAAhB=aUcu0pO)RZoJ_%ou)Z&HMSf17K6%{`}02l#8B~;|eX#lo+Y#**it< z+I7(F>q$${pY#vZd?~^KJwfFVK1P<((&Db!BdALXqm4J`l&#me>nTl)0B#H_nmR8k zymK8Kv(!b;9R_M8{oY9J8Iwa$o1cTdPEUrQ{2eyxCa*dd0FV1k{ESc?L#u;+d0Bw!yc&yzpxp(|-QX-|^=^Avt0|UV3Xt*0s%i zQyw9zBjy{gwO2ay4H)b5Pi8io4Jwn$jURRu1m01b1l|F1N7qcQU6wMHYN24uQx%64 zhPcvmUTRt;omoe}?1FHnyfn3&!NtAi2G_W!?r4HOKhCeC6eV@k4y6+&B^3G^W^k-NgEg!mkCL} zuC|ggq==)3;l|t!f-+gsB%O+*oFq=_lkpTjX_r~jWz|3vO(s$mefg)y^&6@tzm_xJ z7JhSCF8+LhO(kngOTB}n(T}c&GQ7%0Ve2)&XIaB5d!%f)BN>IXs82P!IG#8^&8xdB zU4bc)79VAUttW$`kP1iNn=*Sr8twKW#Yy!AbpKTl{FFJ~-3SJvSpZCj2-RUhi`@;J4jiMh0gYXLptQ_xY=~7!Ulxt3My6=gr0GQtG8Qrbh7{*o#v%)DBhCLqrA7*BOW2>zm5X&k9co7`OKzOeuYUu7Hf{N6?3NORfC?%{_=C z;;yt4Q#bpL^?{TlN?s@eqSzBgcR*sX8Fa#s6#k47b46%b>l`E`c>jsuH#DhprL z6n4)~xsh`(+U1wGkJ~}GQ)({$7uRTX$S8|gPO#Yf6@ZVsM|)wJARY!4eSkh!-O*>P zRe?g{mA&z!#6jkDX_5`Sa*{ccJO z*gRbRNScxKcr9z3rKKXDt>kT%3;mPwv|*hEF+W2H+rJ$`{`IrQe^Q>||Bdp)Ka}r> zfHbE8kvb}%nHYvDFi-w94gfqZ&51$#Y)aSi{I`(@v46!5aShQ32t(=;?TvGN|Tg>Y=r$S>NZdx$Y*L((*sf#2ey z$YPc3KSH<{&>#Wr_)=9vUt%*#`xrBDRc{_{IE$t}S0$yS&;3w-HB8rRW(*n|r{Qw_ z)q7)Nq`4`@$(aieuen)SG}jyt9p{>6;k4IN)eWp_$xxLI$G^p#L3A(zE~8x})ZkpR zEuQ@^shLf{1s~?O89)A7Z0Ua__~O zfJ>9Sac0iCzfO4;!oh2%HHqjf2~{$uNf0dH6wZ>+ad=eHF=>;$P#JX}CwYz!ufD)2 zs_Aqpr;P+rLxuRcfgFtOQ*71*VJI(XzV0vGW~;gJLPNII>_&@8WlFi}>!`BOD|)NYD@g9d zwwblta)v^9Vumg>w?z7^16dH=AF>cS&~vKC?10W$jH7CV0kv1oAUpd|o&@FXBFZHb z;8HLb4_P*qRx0$rp7=o1-zMzJ$QRi&TuIK7!>BXDaIIz2sc)mEYqAn9nYNrgMXXAa zdW`o|q~$HG&2ADC0A8%+uEdCBhv6~q2T2*HlCI9Aa!pdlVJRd^BlOF>$?}{b~2fBOe!D*Do1pd{veR(XN+E`*w^O9Hj^8BpGzxE+= z;Q{Q3x&p{oQFAKnVQ{ZGp#IvBkj$vqKjCNvzpkTL9IoCVf{%jY zz3^J_xCeGlMxDcWmUBt^;=PS~4E;JC1aPBX(_}chrk-gB^$lS=>x>XdJ*pK%$_)wN z&?QI3YPFCd{6YNGKM|jgh-`F3J`zD5k{KyKqD@|0Xj$@%J)p^!)mh{{`lr{;U`*0WYH8o^=YatnqWK8SDdLCl4F8t!&_^yK|R8wwjSNDaV>(eQ=Q>ZwU+Cms%_VH2C61_O`5k9ZqiUp z=fj_z2=E{fd}s(8RR4N}jmOYT9^b~*aSFAOTsjdb7nh*wm;Wq+pX5>BR2Q6W3o&7& zeu^_XOFB0X($gD=fQs78a$hv!acbe*c}ZG*d6XhY2LIc%V1}U z2cZvq4W}>kwM?Hu3MfP+@W+l--sP`=%WG%omx2_NF}Zs&@-f=cNkjE>Atd&1Lf9mr zcK_&Xow23G&mCuHsCv>|`e0{0-tI}59Y~j#x_HTRR|0c`9^V!tT^|qbTzYEq70{57 zZ-F!b-eEHsshkPx4dTW5>%!|lG}Kw^LE5;&Sac}EjLFD2e3AgUhmin1Ln^$TqVLM} zZ^=8KIFgKL?osr5`s>;ibR2?jq94P1HHl7)w^@@VSER)V4dNi?#HRiWTbvya3Bf>s`1sd1luuU^ zvYv|e1FGB#$jPuLj*fUP0GkeWhUBje$U$==dExN=pobMoJBxv3_0LgO!hR3InPczS z48A{bkO8F*SpBcWlswkNZTe7ZELdtNc4@*czuMyNTo{N$Kr)hdg=D0mB-q>$BMk+` zUx6cQ4V1*&LWdc4ZI(vtj3EDd(6%`PNsh7kn;@yx!TjfK4OeF3Y&m^jrhE!7PCdp$(gtC_>|?}fqV;; zJfD^g;=2+?8`Vji&#?zYvWt*CYqfJ;|X#XOm@Pgs-w_0 zYHAxuogGBXB_kDU{cioDq#RRaagB4~QiF*QTq(y?Oeh#UJuI3ZU-6!Kv~c~KLiK(`KiHw!uioHQ=z$By;982`_?#ch$< z`FUH<1Iv8>X6oUr=eduGa;wu|b>I$REh2N|qQ zIQUy;o&+)(C0hhP%bpZPLGT%xf8<0@=cgevP`o$AD0u zO{`XlE?(%6KA zLO){4%2~TrkH;ZWZqCjXTs#A0``hyhdTz>Wy5@ql7I5>GYUxtjv^0?*#oW5({?P*< zNpz%nMFzaHg?V^DYcXAmxz(C9-VJ2LgZIF)cYSEYQ~1RljGiqTFryD=#5u}<LN}BiyaCH1<*W7f+pO;W|+(thcO7fa+NKbIqN3bie zYlNd^ITc-B>&h@RXaqyjS}>fJ84{LZsTE^+HCR_(aRSV>6q&ZQ5ngJTHBNlE80E5kY>NH@qjg8* zs56}08E?+~jk_!yFo~(hM(IZ;chF#%0XCfagk*oWi44DV!uzH?%WV@07 zxmY0X;u;$H1RmpetWucwf5ZYM8HW#YMM)ts8jHsGh-|)IV&bggun88_wLr>YWGFJ zLbHoSP9V5k>>SPqe?Xr*T-~{mElOmBoE!8LzC7{x{iHP7+4{bnY*GPu*2x3&;l3D9 zk@{%!5VUr{O{XcstH3MizwQkep^g5*PA#&aL*!L963vl*&)bvm$VwWl!j|SJLZ13N z7gXvkPDBJQg=>Z~HTkzKAPWxbu4yYv{x%A2*7Gha;KYYL{3kkyBs9@opc~ZT@BKl&y zYX2u^NTr=&I|J1mSGM>tAA`p%Ek9k|lC{b2c5e(pqHrt2oHd(Oda_xw>T_=?3ZZ#S z#Yb>Kxrr8B`VX1|97drpPdaf7^b5S+ucHN%XCbH<`JpLs?la=He`9iz^h@ic>(C z#$&Wds-2?)X@ky3`+myk&+!6Kk579UgBb~g++Imke34io^N5a@esL|%-@>Y7@sJQC zj5F`z_zMkO(9M|2RBB0Q$ghpAU83!IFF(+S z`fVGr0nLbtz7|Fcib8SHi6t`%d6P~`eD&_yx}`^T>!hHmEZn2BH+}-9>v|epBh)s` zNd`#w!w?r@J&$PrhP-Z!)WY&G-7NUpcDXM&%J%zw!=rx=Qza zQ1HZg@Fw;Fcr`;TJ<#GuJV9CYn1-%wl@hD)Du2`oDCVSbZm)qj3H$K0<2+M1eN2h- z#Sdu~Kdc%Hv3@hkUxx&sxs~qdV(Pz1x@4C{UO-sg%&$Vh2uIOpc&mw!6>YenMk~ zh${(SXKpUZQ_T?c6-g`Q8aWxvlc|4P+rHcPN6TQ7(_>Ps1A*obXy$g$wRdlwxSX|2 z*1N}=Ff;nDc%JSAh3xskGTi<`o6{5CGho%kX6x!2?P>Zf zb*7$#LV~{=_E5dkG*r7jjLZnYRx%?xY2`sW6RjqyXoG4mDQGSUMr#!JoSQo*h`vNE|5C*O` zt0o)|;4VQfm$I6hw6#1rs6Xr2IoE4$CkNa)R^L#CZ>8M0&=pvbP&S>lG1_2^_nA78 zQ{{_BMRz+ANJuxbdN>>j6Le39S0#BF< zw6Q+HrPJ{TEQnqL)y%@q&s_3PhBp?a=e98A^kLypx3~)0gd;1$x3F#9#O2gKQe)k` zC3)6O3}t@gCHlgDvdXGschrcVqSZTL=jYD3>!v$^uyH4O){`%5oU3m{8R*ihZy=fc z1f3e{&3%uI2Y8Q1^7C7Cwq4r|7lxX?rq#%gs|*@d_*oZF(sRZ^m7l{Pt-yW@!|1$i-ZDsaod%FiI@)E2-|{mN{DUN z1Krpo{X6Q@b~Iz+58A!?{%8(COCZkmC*n6K2)D1FuW<9*6ttPWlMC&V*9R~Pe1fWb z_xo`g=&w1@I&G(pP-V1t4r@taeRPFYI;=DH9ML0tD^6Yi0#nlHCLQVBQeu3E z!wL#|N&i2NpnFt2`-fi&o?b9Oqcw@U{D%C^dHcHmp;2VGB-ZnP0B0He|5Lg9-_$z) zUGDxHYyQ`+)x@;>&BWxz=kxO&wwLGnd{mDq zh^6{8RZw7{5ll)*L5!a)p|yNDjl{&6M2|X182^}M(z3N!|8HK+Q)0*l%$h^ijl;%~ z!$fspH*t$3%$HQOQ@oQAYt=z)IFcNuuzZ_yZj&j| z?WVRrK8>_%oz_NWgoXID{am^!##_|K>G%2pcvZPsa5VXt06zd2c15DbjoU|s#|g!h zcbuof4mNWL_P1&5lHEct?WW1-lc+I9Oe#lq7nN(<#UhteJ5I3Nk3-6cy=A{?Eub|M zYKjUK9g2C})-K&f=X20%C9yyEQt{Mx0mq2p--=N10y($H7k9oE zA`9D!`%>EaYFyia*e`$fcBnA)MH!6Cqbu*#rB1rf| zBRFF;ycD(U;YFR}k7J|`4bQS$J%K499A$!DZ(P(7sI*6^Rykn3%@zbdMiYPGJ?EiY<|p#YsuN&`FAZ846w zHp(ZT4}qMwl#dU^k1zI249cfkG{3@JI6CU5a_-$@>t2?Wd$h4BNTdD+Th`yoz4l^i zd47hZbWA*yP*U_r5#l)3OKGBAUb0$m0^5#keNQFPW;3>3Q{WD{m=`%dKIYgcX;T< zCcHGHP?*CL>AzVL6KrA;ZR!hYEG7-bomPC-MkO#K@fS#p`&kWUbK6GSVwP{ zlzlgf%M$_&iS^m`ATJ@y(s@n}Qwkyb1h#-*mVa5S>J&~X4i)YhNz+%xDe@+94Rj!y zhx=(1BNkp&pS=yg{ryuDJa=n8QuzBkYW}-QvpKB9ay%nyUhL2e(h?&>dl&BRQ9@*9 zoGd>1LcvUxAdzTO-Eq1L?-Ss<`suSRhOradrhTKq-fiUrn|=D?^% zU-u9v)WQ;TO4qoB{P4e1)j3Vda#}|9NyvuSC;hS?!KqXqcjL7imu5MT1(mMT{4u1I z$Db#e>sUdopK+n!4?BKT#4Wfmn~Eaj*PZVZ*Z%=Yk34B_m`h{R@tsMWl#|dJWu>k?=%#n^{|@aL)G9oO4eh3>My7J2&@@l#>fNm*>+o_(yxp^OMtrOj^5ld*`(Digd|# zzfP^a4FYm!g88AKEaZUO_R}Pe6x0^?^&xceK~{}q@x_U6)hFQiL3Y;ox_oW0Ir%(5 zciqjHk;^lIl|z)coJa-G#$el;!@5MkJl6F&>{tlEt}c@T^z$}Nl6t2cJ$F9T%2d7TqsxDfuA*+bxjl+(;$jvKPKk<&rLkf1?q`YAF>u25xdZ@(oKoIZX-Z>V4 z$#4ejqZQQgMg>6g(-yhrv)lZwb@hBJ>Cn_iXva_~i0j1bt#QY>uv*dI$nzJR?-r>R zXXe}}*Ia1Vn!|C4ul@zQAXQ= zi|*3-UG5ae4vr?wB%mGg^vbG7Dluh{w%H~5 zM_gzo963e(2j(BC0g^WLH)yl2$bysA3)ZuirJ$h~swe$ELWuW8>(%ee2;`=(1!oyE zG8^#kaBFG~5`nhxjgl3+*hpQ?Aza3+92Fdmfjvv=Roc9&KiBoP!z9`JB8P6pFe)wZ z`yri&Vw-OmA#1@Nw~z-1O7~w`NI82f9sQVEed?dEC$;B7g~-_p#vIf0k9qNDXaOcH zRvyBmZ2jt~{n?5l5_g@F;|Zzd`8-3+m~r(2aa1D>MHI64d`u8aZkiul*IFV{DijeM z`T12|t`e|<>eJnO9jepalv$I=s}r9L_yyFzOLzwrxM;^iyCYh6yvw})bfKV?cpZ8E zCc{X5N)r1LM?j6UgF3sQ=qk}L-y=)99>$rBpVwI@ubQjQcTSg#sU*~?dWn)(v?L{A zYn&&l_keMi{J-ZPc^MoSQ5tD*xBxC4i#K=N9b)5)*>AH#%4IO%cQF%AR4Q8?XEp zySCNAJj49b_A+ht{e80i``2k8zVIrLIuHw@1%a4J+$2&OG%!sBL9Ymq4r<=t>YA`w z@s$;k+W}H0DI>c>SVZ7kvb2^_Ewo*0ME_g3vFmv?4P}$r;fR78Qw^XNKDs<9T%% zNlSamq=bt-WEhUZLewjWfOT=p%)ce{35l32u$1QR9PIqG6$kT9l4PCIf|5g|DtV@c zvjthI^)P}$n9gimDU*|8hWC+)Qs(3a#N;1EfyoLfam@nr3;}1E!Gr^do(6ElWV^L2 z^rq;Aftw2&7?)EAgstRJcrfQ^=8~gH)8GuNyC4I4h2QUUI6zK+bLL-%&x+I10%Ue#2czB;^;lm${w z3`!21g@%i|ki`5$m?W$vX;EhXSIP$9Bp9?)@QVFPmJ~0v{>H>4xtRTL?|GmVQqgJC ztJaf=ob3KfDhM4rt*3l6K}*N-d--O4p!ts6aprMQus%@MgxCi4N1WdM@?)g*cnRiH zN8<@|Nh%LT4GJ*6BM?EB^up=!(mFBz4H+~Du&;`wp{Q~o@?x`zf_s<%@gR{gPlMH2 zA^9c=W%>TTBouAZ6tl>N2A-!pRY)TVvy@ zme~!BiR>}KGBQQ7q{otduI7l_q%#Y013-HL?=2L!Y2_ zmxeBh8?qqpS-1|n;^)HCsS+bTV#ln>6PV@1W)!S1;2uUv4e)@bs$Kyndh135G~3dPrJ>!a9=Ss`5ZUcOPL$3j@I_? zqXH|rN|@mf!5=#ovYTyvoGce^P1aj!q^FCb2cICST|}}w_RDYvX|d|dINZtx#wNgr!|)~1&C+yJ%@7rI>Bf`Mqi*yo(v z^69MDbCv4@Dm?-LhxMlvE*q%OC5N!Suk;&{v`<&MT)>rq>XF!Er~I6StK^HC4@v;{yT{+cgi^W#C)r z2>u|DZm#;%g{rfEi|tH~+zk}%;1;~1!>#;VAPhj!2E52TYZ_ufLXm?iS@sJxz|aNs z>zz*15LBvzV%3)FwX$s2zO&*8d9zyUXB$%83MWq_LHWClyfT%ws#vUZxeq7@j)~vI(ZBW-?z9nAsoL^DL35+(l z{$lkXKa^Fypq|ToKe!o&0s9VMf$CmS>gA+x(*#*BSTSB}IRqsGvl=WSA7*A6mX$HeHw!bxWJL#aE3i==Q9EQD9f*Yj(31V zasd_8`df?oFC~0~P**Wt&Bb{!Fu63!VP{71lIE9&xqZ7u3G8e)j_KEh-#4LMw&JI%B(Pe>rwou2?rBjM095mX%YE%)@9 z4q~NVKNLho3KWnc!;JnyEThW2eiz7wfymeI1z-8Vb5?~evT}5^NTn~R7{Gr=6)@AJ zLZYWdfdM5rMy#dMxYsX=vHPWO8JK7vZd7GS(cDn;Cm;0% zN2y3Mfyy4?%Tnw1_p3sv(nuO}oSb>rC^6~H$NI_ZqVMT~9;mgDCvSsjw@#DQ2#ty+|e-=f??~cvD0S02EJo+-sep zLG)UAa~94EQ$txU$TPE|VzN#T_j!IN;j5g0UX2>BAYwS|v0TZf(3gOON2R8kRZi3~ zGf(q@#RvdpBc_4j*?SAj(()(2#SXOvZ|KO!%(9O>Wn%riMN1}38Jth`KKY7Lf_qYY z0)WNyoyi|Er(kumSi67RiGhlXFJEgwMj2BG7aW9A{EX~m^mYdQcPJ*+-Q`F$J zol=P)jlWpML|+=#;XL?B{ZBSmd;UN;tU1BbOogiYStNm!d*#k`n-o=hh)aUYl~jx?KSV>4h`M%UzP7Mc~rgfT8Z1BJ>x%a74Lt+)gj@C zdeI4-{1;f-^n0-R`k9&z`~gh==S%dzHJr?C?3|tGl?2qp^=*u-j2-?5RMLR-QeIm6 zm-CZLL7$L<00s<`K`ez)kMJi70WpZ=4*{eUQN~5AbpNRlk_j0Uoyekw_0~Bc4^X9| zAVo_@5~bV`u)aQ3-t;4G`mm^Kw$OayKIvwswfBNw0IqY`4;r&TIp7F7{pY)93 zht$LCx*2h1*^0R^7bO&8E}~2^NoLVEH<3~zlqrfS|Gd>KUE)hW5UX1%i!-TadGKP{ ziSc9g%8ZGADVRLZ+%3@HCsWHpjxYnFMJb(*X*ebMi#E18224wruXL)MrnX;DB%&+_ z3&uJ=Hxamf1XjgRD)CMYt8p~qWWiQClnV#|NZCdSSRQpu=d!8K(sAdI&3gQ0d#y`m zL!0|Cc%MB3%OY$wm2^y&&Y_*+WWtRj)eJ8U;3^7}Tp*Qck+x!`Fqv(e=a)-wt-OdM zBb2GRoK+RxprDi)7c=LdF7`2>PHBS<^xiNo!3EZ)m=yumCMeXR@zf%k5;Gc-#n(PB z(B^BO6ifL)%)-6{2L5?yi+dnu zy%W#xD5lMdByE=?LRonsQ#=zV>f%GXiNShg(03xqlKo-v@~^tCxZm?>1XCm{MnPu%uFSv48w|rxzeSD_pQG)6&14SuBAk0VxFE$&asml=?PyAAhD@JZEhk+ zxYF{Keen&86a$I2d6Kv15U!+bkE$|WZ>P<~c;X{n#)lNWuy>lxfRdMV*I{`uW&1G7@JMWM65WPi}-PyWO*NYc%IBC zA4D}#&9_6Jo(k6-5hfpoDp-U&h`#JiLRFoIcxdr78mG9Z))y&xE2le&Vi^_L4JY2? zyR4QdMQsdHk}q!%bZRAP<=cnid9&i2gbST?Dr@L1%=_VrihoEv3iE0S(GG)+wWFsy z4%_9D_e$QcF$@7@ofuqz_()qVSGq-)A*|&;O~_VQZwb+I$_V_jZZ{g*?IErq&TRQn zsM_J;r3pLBajb@9Q^YGPDq-eu2iRl`*z4-1KllMxlBI*_D+dZDRcWI*ZtGmnbwPJ~ zN!!-VM7Q;cZga$10dJYIqDYi32{to4%SUM$YRk=@53W{GBx}0`685=!3l|ybf$$pO zp6*wV9($n-Sx1A9&Jj2G1zM%A>@7vn=iv<{<*wGpG4-~iqv0I;QFtb^>EiSE!okM7 zNcm70xyp5Rk*g%{(Nn0+WcH1?PT>d$9iDD)wTiEJ%-v{u1-xFb}<030s!JezHbS_$O zt(*k-Nav3Gy~x}|1zgZN7P^|S`hLuP<4Wd{Rt6H+Q$Qb8EEXKYRN5*hVJ5p9RmRP9 z0RMKeqfsL(n)Be5+lB4Lm7I%k$`DCxG*)z~V;~K3%)Y%j9EK(jL;eatAI6aI=14A$OrMU^4GW zuxXDRI{NO%+rwhuGVe^heID3Qv|P<~O0;XF3^(1> zf5==`^Bh>V^Gc765;LM=rV}<++y3+5| zUBeF8al&;74zN*Ey;Td9wX*NMSp14WhIg`K4kxTb2TR&rW7%e=W`xC_+)^Y3H^;6uFR*0U?ta7TV4Q}QrUlBqbFwNCAy7SwO?MX>} z=oYA|WAgfDQ|3|`8RsH~k@6eG&CkPVcsg!}7qv};@B4G|@f8WNxVlPt(`H=bhy@9@ z50sn9b%-r1jAuKK_zA)cZi(9m7i-rV2_#b+i?!AEmF1&briu$VTX+Y5 zt_h^aMPf1bC!-x_9K@Z-xMX4Fa&Xh@s=KQqx(u`_MkoPU`00HOT1!Q0XXky;>73*m0={#`kgYFHBL63X^~ zyecLzGQ+>zz}4Fhbkuti#9}5fIPp-LB8Gx0-4q^&=Prt^-@i?u^Ecj!@04Y&rf2Io z6PgOy21%eNuzNAf7=~1u&!J?MbgpT*+wXfxi@luvl9blMFbFeK(A|~T5=2fVD=)rV z4qGk#<1b&p?%cDo&KV1s+r3#ASm`zsbD-U0rW{4L6U#q^Y%T~q6ziZ1%2{m2887z| zvoObS%$px9_o6%Wc5RCF^kyY@pfD8MX&Wx8knElm>gvvfs!!>(Wx3jMV`|r>+v;We zFL|zscLeLah)vKbh`QI_pnrH{h2z;}@p6@-%k02+qYv;A|B;}WT-gJ4&rF{HkKOQ9 zM4rqPM@43r!GgHL!|5*T<|B9R%5k@0@QSc-!+a8Rk;_N;;pE0g5-C-ho-w+4Z}^hZ zaz4qHLMDXa7yH=eM~frhkHmOXa3lJUq%y*WpF~QX%=t~mi#f`dDn->9*rPWbL%ut< ze>*jM&#L$amrsB)3OcRuGtzwTOZN+r7-YIgSLNj-qa0yO5`LA#(^ zw|3^>T-5tz zA=kxibN#PVcgL=d4kJXBApb9ItQZy@b0{hv@LlB5*y7rKg3k;v1*czR0Yfm!IK32U zR8>5I`QhWgF}l!6dC+&;7&jAOhaGw%KMvsZcmj|yVDNee!^1}y(t{Z!3^mB$6MEnb z+rH3vR{aHq75uAc(jORrAf*M67G`H6TQMH$kjW3IXod)B2J|cYmCS|~Rs(N#X?P+q zx+7S+VLmE%yA--1-2q`DHB% zDtOnzyuN--KZ9D_%F(5H;HLSY>P|GcIbH9!+|zw?b?ejXOs=LYmPydO;v^BlcOE)# z<@dIU{8e<~e9fR~iwk^xHbxdKctCQX;3q6<3k*z+HH`T)>XQ*W;0{SSMQS9gBbe zp?2mCyuf~@Vx7UWWG$4MXZRc5^SSqd!FQ^65-70&E7`WRv>b1ch7~RO`}PdkonQWU zPAAOBER^zsB#u~x9TO|HBvs`CjwtPLOHu{$DL0Hclt(gQ))iQ!3`dA>&j?x}1sUD# zA+8$B-&CVQJjW=D_6z zX7@{btRIb!<8qB#AGgWNvawhE0bda{|=ab3%2?Gg4~7J(Gx4o8?|veGC=yVC#1!-EPE#p~@c|$Z8vz$u<%~;NVEM#zJdw@O_g@cl~Y?zb&B0@fa|>7m8me=iy%AK zO3jYdUp8pd6{|f=kbK$%DlmhPjAY?$3~JonaR;%4DIllbhj8vLkq64gaN5!<8gXDDl63@w8XRt}|a`CfO zn%TYz5V_Hav{@20bfAHmJ^Rx%KpTW!7yblf^k;u#%r_y(%oi)EQ5SM*a89lCH_B(o zJZXSd%~F{s0YoyIIGv&a1&QYPqK-^Tp*s%27XwDo?fKH6nfXb(u9bp( =dyfJB! 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 literal 0 HcmV?d00001 diff --git a/tools/cooja/lib/log4j.jar b/tools/cooja/lib/log4j.jar new file mode 100644 index 0000000000000000000000000000000000000000..1595a56ef29615a8e3c77827071a03500cb32fe9 GIT binary patch literal 350677 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�vRB1&TowiM=z9E9ZoEJ)Vu=C<>=C2UyKD`= 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≥vwhA(>I4OQ22SvJ&ZFd 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 literal 0 HcmV?d00001