ProtoProbes Appendices | Graduation Project

Page 1

appendices of ProtoProbes By Pepijn Verburg

Master Graduation Project (M2.2) coached by prof.dr.ir. Loe Feijs Department of Industrial Design, University of Technology, Eindhoven, June 2016


table of contents appendix 1: global planning

3

appendix 2: workflow descriptions

6

appendix 3: results contextual inquiry: workflow

8

appendix 4: results concurrent probing: wearables

10

appendix 5: results concurrent probing: sensor basics

12

appendix 6: results focus group: data visualization

14

appendix 7: consent forms focus group: data visualization

18

appendix 8: activity map

22

appendix 9: design space

24

appendix 10: shape exploration

26

appendix 11: prioritized feature list

32

appendix 12: data embodiment sketches

34

appendix 13: 3D models

36

appendix 14: PCB schematic

40

appendix 15: PCB layout

42

appendix 16: ESP8266 flowcharts

44

appendix 17: user Interactions with data

46

appendix 18: visualization unit code

48

appendix 19: data collector and receiver code

2

102


appendix GLOBAL PLANNING

1

3


months iterations

phases

results

September iteration one

October

November

December

January

exploration:

iteration two

business model

synthesis and visit preparations validation:

research: visual information & Maker Spaces

in Maker Space

research: maker spaces protoype:

protoype: ready for external party

extensive visuals

demo days

versatile visual framework

protoype: seamless data transfer

prototype:

user validation

initial visuals validation: with students

proposal initial framework

February

4


months iterations

phases

results

February

March

iteration three

April

possible visit UmeĂĽ exploration: business follow-up

May

protoype:

validation: follow-up tests

follow-up version

demo days

documentation: report

complete platform

protoype:

user validation

follow-up version

version for external party

validation: initial tests

user validation

June

wrapping up

documentation:

final presentations

July

5


appendix workflow descriptions

6

2


Designer engineer tinkerer

Problem is of a societal nature.

1. problem definition

Research related technologies

Research societal context and create representation of the people in question

2. background research

Determine in context of people

3. specify requirements

Perform initial tests

Determine from performance expectations

Determine in terms of functionality

Problem is a technological challenge

Problem is unclear or an experiment

Research related technologies and similair experiments

4. generate solutions Focus on societal impact and conceptual qualities Make mock-up prototypes Focus on reliability, effectiveness and feasability Make initial drawings and schematics

Focus on feasability Make components for the first ideas

5. prototype solution

Collect required components Build breadboard prototype

Collect and make schematics and drawings Build proof of concept

Build experimental setup

Test in terms of expectations

Test in terms of functionalities and performance

Test in terms of influence on users

6. test solution

Reflect on alternatives

Reflect on performance and optimizations

Reflect on changes in societal context

7. reflect on results

Focus on performance and improvements

Focus on performance and technological innovation

Focus on conceptual qualtities and societal innovation

8. communicate results

7


appendix results contextual inquiry: workflow

8

3


Participant #1: Problem definition: “I originally started with the societal aspect, but in this phase I shifted towards a more technological focus”. Background research: “I looked at existing products and how they implemented certain sensing technologies. In my case I’ve taken a look at the Beo Sound Moment.” Specify requirements: “This is where the societal aspect comes back again. I look at my user and then I translate them to technical implications.” Generate solutions: “It’s a guess whether a solution will work the way it is shown on the datasheets; often there might be a mismatch here”, “I test some stuff out” Prototype solution: “This is a phase where I tend to ask some experts to join in when I’m not certain about my readings.” Test solution: “Now I’m going to test the components of circuit in between the prototyping cycle.”, “This takes some time, but it is worth it”, Micro-USB connections were tested: cable in computer and multimeter on the the PCB to check for a connection. After this the multimeter is used to see what the resistance is of each connection to check whether the connections are properly done. Reflect on results: “The connections are not beautiful enough”, “The colours of the wiring is crucial”, “Multiple PCB’s are lovely for technology integration” Communicate result: “First I present the result. I also show how it was made possible and how choices are made.”

Participant #2: Problem definition: “This project is about data collection and interpretation about how people walk” Background research: “I collect information about different types of sensors” Specify requirements: “In my mind I setup several requirements for the sensors” Generate Solutions: “I try to imagine how they should be integrated into the wearable and how their functionalities will probably be.” Prototype solutions: “Testing in-between of the different types of sensors is difficult and takes a lot of time” Test solutions: “making my own graphs took a lot of time”, “making the graphs cost more time than interpreting them.”, “I need to test in different contexts, especially with a wearable.”, “The wearable aspect make the readings very unpredictable.”, “Exploring filtering of my data takes too much time.” Reflect on results: “I just view the visual data over and over to look for specific patterns and properties”, “I need more variations on the graphs, but I simply don’t want to put more effort in them”, “There is a big problem where sensors tend to influence each other forcing you to test them at the same time.” Communicate results: “I make print screens of the graphics, but they are hard to interpret, because there are 5 lines going through each other.”

9


appendix results concurrent probing: wearables

10

4


team #1: Only two of the participants managed to hook up ProtoProbes 2.0 themselves. Other people were confused by the two wires coming out the same location on the object; even though these wires were color-coded appropriately.

team #2: People dropped by with only the sensor and not an in-progress prototype. This means the sensor was not powered yet. An external Arduino was used to provide the breadboard with power.

team #3: It was still hard to determine what is wrong when the sensor was not working properly. It happened several times two sensors looked identical, but were working completely different. Probing around on different parts of the sensor helped identifying loose pieces of conductive wires on specific spots.

team #4: Everyone was amazed by the instant feedback about the functioning of their sensors. Other participants were trying to get it to work with their own Arduino and Serial Monitor. They were under the impression their sensor wasn’t working when they didn’t get any readings. However, ProtoProbes 2.0 gave readings, making them realize the problem was the Arduino and not the sensor.

general: Furthermore, people didn’t expect the data to be transferred wirelessly.

11


appendix results concurrent probing: sensor basics

12

5


observation summary: Almost all discussions were about what kind of sensor would fit the design concept the best. The lecturer needed to get a clear view on what interactions were involved with each concept. The students have no clear idea of how the sensor would behave within their scenario. The lecturers point out they should test this out and them pick a right solution. Most students agree with this, but are not sure what (type of) sensors to buy for these tests. Many students take into account extreme situations their prototype should be able to handle (e.g. extreme weather conditions). There is a clear distinction between input and output. The lectures aim the students to find a balanced combination between the two, but this proved to be difficult, because they often choose a familiar solution (e.g. LEDs as output). The piezo element was a common choice, because it is easy to integrate and can be used for input and output. One team wanted to recognize rain using piezo elements. With the help of ProtoProbes 2.0 they quickly found out that the surface on which they attach the sensor is crucial in recognizing rain. A static surface (e.g. the table) would not distinguish light tapping from flowing air onto the sensor. However, when they placed the sensor on a flexible surface the data turned out to be very distinctive (see figure x.x for the setup). Students noticed the piezo element is hard to calibrate when looking at the data of ProtoProbes 2.0. They immediately questioned it becomes hard to program this. The researcher needed to explain the concept of the differential manipulation allowing one to recognize change instead of absolute values. This group didn’t know of this at first, but could quickly understand how it worked when seeing the manipulated data stream. One group was doubting between a piezo element sensor and a dedicated pressure sensor. For their particular application they came to realize they needed to buy them and see how the sensors would behave. The learning-curve to connect ProtoProbes to a sensor is too high for this group. For example, the concept of pull-up and pull-down resistors is still unknown. During the tests with the sensors it appeared the visual is by far the most important components of the concept and that the probe for this group should also be the supply voltage for the sensors. This was because they were still in the explorative and definition phase. One of the lecturers suggested to implement a software ADC conversion with a customizable threshold. This allows you to see a digital signal over a period of time. One of the lecturers stressed the DSP is the most important component of ProtoProbes when more experienced people are developing a prototype. The students don’t have a toolkit to test out different sensors. The lecturers suggested ProtoProbes to contain several sensors by default for testing in context (e.g. accelerometer, LDR or temperature).

13


appendix results focus group: data visualization

14

6


Unrecorded: Julia: “I normally need a trigger value I want to test.” DSCN8482.MOV: Marcel: “You sort of create the mental model for the sensor. You think of how is this actually working and it depends where you start. For me, I start with the numbers (editor: 0 to 1024)” DSCN8484.MOV: From Design to Sensors (spatial pressure sensor)  Rickard and Julia “Oh god, the default value matches one of the pressure values” From Design to Sensors (spatial pressure sensor)  Taes: “Every sensor has flaws in them” From Design to Sensors (spatial pressure sensor)  Madyana: “The look of the sensor is tricky, it looks like a pressure sensor but it’s not” Random (spatial pressure sensor)  Mariano: “Something I didn’t know is that when you press at two places at the same time it shows a different value on the pressure sensor” From Design to Sensors (distance sensor)  Taes: “That would be nice to know, why it was made in the first place: this was made for moving objects for a certain distance. Now you just need to see what doesn’t work.” From Design to Sensors  Julia: “The combination is smart. You should combine several sensors to get what you want. You can’t just buy something that does it all for you.” Sensor Understanding  Julia: “Many times you rather adapt the interactions you want to have to the sensor instead of sitting there for a week and then realize I can’t even do the code.” … “It would really help if you could see the data in context of what the sensor is actually reading. For example distance with this sensor or luminosity with a LDR” … “but still you would it context for people”. Sensor Understanding (distance sensor)  Madyana: “You should instantly see this number in the code corresponds with this distance to the person” Sensor Understanding  Taes: “I was thinking of an animated GIF for all of these sensors … to actually translate the data to the thing you are using” Sensor Understanding (distance sensor)  Marcel: “What we are trying to is to figure out what this guy (editor: the distance sensor) sees and the only way to talk to me is now through this (editor: points at screen)” … “It looks like a camera, but it’s not, you need to put your mind into this guy and ask yourself why it is not working” … “it’s like this relationship with the sensor”. DSCN8485.MOV: Connecting ProtoProbes: Taes  “How do I turn this on?” Data Embodiment  Taes: “with the sound sensor I want the data to be like in iTunes” Data Embodiment  Marcel: “If the data is linear I don’t see why the representation of a graph would not work” Pattern Recognition  Madyana: “I see that the three claps are there, but how can I get the three claps into my code?”

15


Pattern Recognition  Taes: “It’s like you want to say: look for this thing, and when you have it, let me use it.” Data Manipulations  Rickard: “What I like about this thing is the added complexity, math basically, it is a way of introducing what you can with the numbers to get to the behaviour you are after.” Data Manipulations  “I think a lot of time it would be about playing with these things and see what works for you work then. It reminds me of the photoshop opacity settings where go through them and see what works of them.” Data Manipulations  Madyana: “Sometimes it feels like I’m altering the behaviour of the sensor, but I’m actually doing everything on the software” … “and it’s like the opacity settings in photoshop, you need to try it out to see what happens” .. “Right now, it feels like I’m slowing down my sensor, but I’m actually analysing in a different way” … “There should be a way to show the regular reading at the same time as the manipulated data” … “or some useful way to extract the behaviour that I’ve defined here.” Data Manipulations  Taes: “It’s like an Instagram for data. You apply this filter to the data” … “I’ll take the vintage data” Data Manipulations  Pepijn: “The very quickly switching between different representations appears to be important. Is that also something you do in your workflow? And how?” … Rickard & Madyana: “I think this is missing.” Sensor choice  Madyana: “Now I keep switching, but maybe the first choice was already enough, but I just didn’t know how to play with it to apply it to my interaction.” Sensor choice  Taes: “There is also a lot of knowledge that get lost. There are many people already tested it out. But now you need to do it yourself ” Relation to Software (Compass Sensor)  Madyana: “I was wondering if we could see the serial monitor to see what is happening. The lines are so complicated.” Relation to Software (Compass Sensor)  “I want to see it in 3D” Compass Sensor  Teas: “That one is the most vague one until now, but this one is literally flowing is space. And this one should have a representation of the object to mirror the object itself, so can get some kind of reference point, otherwise you don’t know where up is, or anything.” Compass Sensor  Madyana: “What is important to define is how you put in your prototype, like this (editor: pointing upwards) or like this (editor: pointing downwards).” Compass Sensor  Rickard: “This is like a compass right? I would then make it like a compass” Compass Sensor  Teas: “You should see where x, y and z are in a moving object” Compass Sensor  Rickard: “I would like to know when you position the sensor differently whether the sensor readings would still be good” Compass Sensor  Teas: “But then maybe need two, maybe you need one, where you are like I’m going to use this a flat compass, and you only get the 2D thing, because that’s the only thing you care about. And then you like, add complexity button, BOOM, three axis.”

16


Compass Sensor  Madyana: “With the lines, it’s so complex, I can’t see why it is changing and how I should use it.” Compass Sensor  Marcel: “Maybe the sensor is good, but the scale of the data is not good. Perhaps if you zoom in the data is completely different” Compass Sensor  Madyana: “But even when you get the scale right, it is still very hard to get that tiny bit of variation out of it I need for my prototype.” Compass Sensor  Teas: “Don’t make me analyse lines, just show me what the thing looks like!” General  Teas: “I would like a checklist: did you hook up the resistor? Did you plug it in backwards? Did you adjust your location? Did you move it to another environment? With other lighting conditions? With a different temperature? Did you maintain your emotional state? There is a checklist for each one of these things.” Teas: “These random numbers don’t make sense, I want something that is more human. Like me, I’m human!” Custom Visuals  “I’ve done multiple times sending the numbers to processing to see what happens.” Custom Visuals  Rickard: “I’ve done that a couple of times, but I don’t usually do it. So if it is like a pre-packaged thing, it would for me be easier to start with, also for students that are learning these sort of things is to their benefit.” … “Now with my students they start with getting the serial print out.” … “But now with something that is more complex, is not really something we show them or teach them.” Data Embodiment in Object  Teas: “Now you have this nice object, but why is it not like a digital thermometer. You stick it in and really, really quick see if it is working and decide whether you can move on.” Data Embodiment in Object  Marcel: “I totally agree, what now happens is that when she was using the sensor you needed to look at what she was doing and switch to the screen. You constantly needed to go back and forth. If that data visual is at the same spot you don’t need to change and go over there and go back and forth.” Data Embodiment in Object  Teas: “For the simple ones yes, but for the complicated ones it would probably not add anything.” … “If you have like the quick fast solutions and then the make sense of something highly complicated solutions, like they are very different problems.” Output  “Can you use this as an in between thing? Like a filter? Like a moving average box?” Taes: “Make sensors?”

17


appendix Consent Forms Focus Group: Data Visualization

18

7


19


20


21


appendix activity map

22

8


workflow focus group: usage of tools by makers

ThingsCon 2015

expert meeting: data interpretation

expert meeting: existing solutions

programming: data manipulations

form-giving: integration in wearables form-giving: integration in artefacts

literature: interaction assessment

expert meeting: sensor types & ADC

literature: aesthetics & pleasure

probe

contextual inquiry: class about basics of sensory data

open-source community

semi-structured interview: exploring sensor types with students

benchmark: software vs. hardware

benchmark: cloud solutions

benchmark: data visualization libraries

expert meeting: sensors in architecture

expert meeting: internet of spaces

programming: data transfer framework

UID Lecture

paper: debug flow

form-giving: integration in spaces

concurrent probing: connector affordance user test sketching: integration in makerspace KickStarter stakeholder implementation

data

workshop: data in spaces #1

programming: mapping & view manipulations

expectancy questionnaire

perceived usefulness, ease of use and acceptance

literature: distributed cognition

literature: cognitive framework of visualization

contextual inquiry: Interactive & UID

literature: definition of overview & insight

contextual inquiry: experience flow

concurrent probing: workshop about wearable sensors

expert meeting: validation methods literature: the maker movement

emperical observations: workflow intent

expert meeting: wearable sensors

activity map v3.2

electronics: multi-state circuit

electronics: analog circuit

literature: modularity

benchmark: connectivity

heuristic evaluation: aesthethical qualities

23


appendix design space

24

9


design space v3.1

data beha vio

n con

y

ivit

ect bluetooth

Wifi

medium

LCD screen touch screen projector

Iteration three

Think-outloud

2d graph cumulative stack

n

tra

sl a t ion

physical controls

multi-state software

touch screen

3d Vector

modular attachment modular connectors transitions

teach-back protocol

a par

n uatio eval

ion

time

paper

contextual inquiry

concurrent probing

user

interact

voltage

stored history

chrome app floating data points zoom-in zoom-out view-oriented binary

capacitance kickstarter contextual implementation

busin ess

uty

bea

ur

25

met ers


appendix shape exploration

26

10


27


28


29


30


31


appendix Prioritized Feature List

32

11


Must have features 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.

Hardware: Integrates in artefacts (from concurrent probing, contextual inquiry and workflow integration) Hardware: Integrates in wearables (from concurrent probing, contextual inquiry and workflow integration) Hardware: Integrates in spaces (from concurrent probing, contextual inquiry and literature) Hardware: Shape affords connecting (from focus group #2, contextual inquiry, literature and expert meeting) Hardware: Analog sensor support (from focus group #1, focus group #2, concurrent probing and contextual inquiry) Hardware: Digital sensor support (from focus group #1, focus group #2, concurrent probing and contextual inquiry) Hardware: Data embodiment on probe using a wire as metaphor (from focus group #2 and +Project) Hardware: Physical signal type controls (from focus group #2 and +Project) Hardware: Physical on/off controls (from focus group #2 and +Project) Software: Chained mathematical manipulations (from literature) Hardware: Wi-Fi based (from benchmarks) Software: Multiple graphical data series per probe (from contextual inquiry and focus group #2) Software: Multiple probes per data receiver instance (from contextual inquiry and concurrent probing) Software: Web-based (from benchmarks) Software: Seamless connectivity (from benchmarks)

Important to have features 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32.

Hardware: Connector modularity (from workflow integration) Software: Automatic axis scaling (from expert meeting) Hardware: Rechargeable (from workflow integration) Hardware: Internal variable pull-up / pull-down resistor (from contextual inquiry, concurrent probing, focus group #2) Software: Pull-up / pull-down controls (from contextual inquiry, concurrent probing and focus group #2) Software: Pausing of data stream (from literature and expert meeting) Hardware: Stand-alone sensor readings (from concurrent probing, contextual inquiry and focus group #2) Hardware: Docking station (from focus group #1) Software: Embody data flow in UI (from expert meeting and focus group #2) Software: Threshold recognition (from contextual inquiry and focus group #2) Software: Mental model views based on complex data type (from literature) Software: Mental model views controls (from literature) Software: Custom mapping of axis (from focus group #2) Software: History management (from expert meeting) Software: Tagging on timeline (from expert meeting) Software: Measurement frequency controls (from expert meeting) Software: Mental model views based on context (from expert meeting)

Nice to have features 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48.

Software: Data set export (from expert meeting) Software: Sharing of live graph (from expert meeting) Hardware: Capacitive sensor support (from contextual inquiry and concurrent probing) Hardware: Multiple sensors per probe (from contextual inquiry and focus group #2) Hardware: Attachment modularity (from workflow integration) Software: Automatic COM port recognition (from workflow integration) Hardware: Internal programmer chips (from prototyping purposes) Hardware: Over the air code uploading (from prototyping purposes) Hardware: Embed common sensors in probe (from expert meeting) Software: Pseudo-code generation for applied manipulations (from expert meeting and focus group #2) Software: Cloud services integration (from benchmarks) Software: Pattern recognition (from expert meeting and focus group #2) Software: Multiple data receiver instances per location (from workflow integration) Software: Code generation for recorded behaviours (from focus group #2) Software: External data visualisation program connection (from workflow integration) Hardware: Output of manipulated data (from focus group #2)

33


appendix Data Embodiment Sketches

34

12


35


appendix 3D models

36

13


power management module

analog / digital sensor module

37


aluminum casing part model

top rotary part model

38


39


appendix PCB schematic

40

14


41


appendix PCB layout

42

15


43


appendix ESP8266 flowcharts

44

16


Boot sequence

thread sequence

Initialize Serial

N/A

Handle Verifications

0.2Hz

Initialize Wi-Fi

N/A

Handle Serial Packets

N/A

Initialize UPD

N/A

Handle Controls

1Hz

Initialize Timers

N/A

Handle Animations

33Hz

Initialize Sensors

N/A

Apply LED Behaviour

1Hz

Initialize States

N/A

Update LED

50Hz

Initialize Lights

N/A

Send / Handle Data

50Hz

boot sequence and thread of the data collector and data receiver unit; execution frequencies are noted when applicable

45


appendix User Interactions with Data

46

17


select data channel

scroll back in time

add data manipulation

choose data manipulation

toggle data manipulation

reorder / delete data manipulation

activate alternative representation

select alternative representation

scroll back in time with alt. representation

47


appendix Visualization Unit Code Please note that external libraries are not included, only code written for protoprobes is available in this appendix

48

18


65 66 /** 1 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 67 * Graphic constants 2 /** VARIABLES **/ 68 */ 3 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 69 const FRAME_RATE = 10; // frame rate of the graphics manager 4 70 const ONE_SECOND = 1000; // duration of one second in threads and intervals 5 /** 71 const ANIMATION_SECOND = 1000; // duration of one second in animations 6 * System contants 72 const BASIS_INTERPOLATION_TYPE = 'basis'; 7 */ 73 const LINEAR_INTERPOLATION_TYPE = 'linear'; 8 const SYSTEM_NAME = 'ProtoProbes'; 74 const DEFAULT_TRANSITION_DURATION = 200; 9 const SYSTEM_VERSION = '3.00'; 75 const INDEX_TO_TIME_TOLERANCE = 20; 10 const SERIAL_BAUD_RATE = 57600; 76 const INDEX_TO_TIME_MAX_ATTEMPTS = 20; 11 const DEBUG = true; 77 12 const DISABLE_CACHE = true; 78 /** 13 79 * Graphic type contants 14 /** 80 */ 15 * Interaction constants 81 const DISABLED_GRAPHIC_TYPE = 'disabled'; 16 */ 82 const LINE_GRAPHIC_TYPE = 'line'; 17 const TOUCH_SCREEN_MODE = false; 83 const SHAPE_GRAPHIC_TYPE = 'shape'; 18 84 const PIE_GRAPHIC_TYPE = 'pie'; 19 /** 85 const CIRCLE_GRAPHIC_TYPE = 'circle'; 20 * Path constants 86 const SPHERE_GRAPHIC_TYPE = 'sphere'; 21 */ 87 const VECTOR_GRAPHIC_TYPE = 'vector'; 22 const BASE_URL = '../'; // referenced from the view index.html 88 const COMPLEX_SPHERE_GRAPHIC_TYPE = 'complex‐sphere'; 23 const PATH_TO_IMAGES = BASE_URL +'images/'; 89 const POINT_CLOUD_GRAPHIC_TYPE = 'point‐cloud'; 24 const PATH_TO_TEXTURES = BASE_URL +'textures/'; 90 25 const PATH_TO_SPRITES= BASE_URL +'textures/sprites/'; 91 /** 26 const PATH_TO_SCRIPTS = BASE_URL +'scripts/'; 92 * Graphic renderer constants 27 const PATH_TO_DATA = PATH_TO_SCRIPTS +'data/'; 93 */ 28 const PATH_TO_GRAPHICS = PATH_TO_SCRIPTS +'graphics/'; 94 const D3_GRAPHIC_RENDERER = 'D3'; 29 const PATH_TO_UI = PATH_TO_SCRIPTS +'ui/'; 95 const THREE_GRAPHIC_RENDERER = 'three'; 30 const PATH_TO_VENDOR = PATH_TO_SCRIPTS +'vendor/'; 96 31 const PATH_TO_MANAGERS = PATH_TO_SCRIPTS +'managers/'; 97 /** 32 const PATH_TO_MANIPULATIONS = PATH_TO_SCRIPTS +'manipulations/'; 98 * Sensor constants 33 const PATH_TO_UTILITIES = PATH_TO_SCRIPTS +'utilities/'; 99 */ 34 100 const UNKNOWN_MEASUREMENT_TYPE_ID = 0; 35 /** 101 const ANALOG_MEASUREMENT_TYPE_ID = 1; 36 * Storage constants 102 const DIGITAL_MEASUREMENT_TYPE_ID = 2; 37 */ 103 const UNKNOWN_SENSOR_TYPE_ID = 0; 38 const SIMPLE_SEPARATOR = '_'; 104 const ACCELEROMETER_SENSOR_TYPE_ID = 1; 39 const LAST_PORT_PATH_INDEX = 'lastPortPath'; 105 const GYROSCOPE_SENSOR_TYPE_ID = 2; 40 106 const DHT_SENSOR_TYPE_ID = 3; 41 /** 107 const SENSOR_TYPE_LIST = {}; 42 * Packet constants 108 SENSOR_TYPE_LIST[UNKNOWN_SENSOR_TYPE_ID] = 'select sensor...'; 43 */ 109 SENSOR_TYPE_LIST[ACCELEROMETER_SENSOR_TYPE_ID] = 'accelerometer'; 44 const PACKET_START = '[START]'; 110 SENSOR_TYPE_LIST[GYROSCOPE_SENSOR_TYPE_ID] = 'gyroscope'; 45 const PACKET_END = '[END]'; 111 SENSOR_TYPE_LIST[DHT_SENSOR_TYPE_ID] = 'humidity & temp.'; 46 const PACKET_SEPARATOR = '_'; 112 47 113 /** 48 // packet IDs 114 * View constants 49 const DATA_POINT_PACKET_ID = 1; 115 */ 50 const SET_FREQUENCY_PACKET_ID = 2; 116 const VIEW_TYPE_LIST = {}; 51 const DEBUG_PACKET_ID = 3; 117 VIEW_TYPE_LIST[DISABLED_GRAPHIC_TYPE] = 'no view'; 52 const MEASUREMENT_TYPE_PACKET_ID = 4; 118 VIEW_TYPE_LIST[CIRCLE_GRAPHIC_TYPE] = 'circle view'; 53 const SENSOR_TYPE_PACKET_ID = 5; 119 VIEW_TYPE_LIST[VECTOR_GRAPHIC_TYPE] = 'vector view'; 54 120 55 /** 121 /** 56 * Data constants 122 * Manipulation constants 57 */ 123 */ 58 const DATA_CHANNEL_EXPIRY_TIME = 10000; // 10 second of inactivity will hide the data channel 124 const MOVING_AVERAGE_MANIPULATION_TYPE = 'moving‐average'; 59 const MIN_DATA_VALUE = 0; 125 const DIFFERENTIAL_MANIPULATION_TYPE = 'differential'; 60 const MAX_DATA_VALUE = 1024; 126 const INVERSE_MANIPULATION_TYPE = 'inverse'; 61 const ABSOLUTE_MAX_DATA_VALUE = 10000; 127 const THRESHOLD_MANIPULATION_TYPE = 'threshold'; 62 const ABSOLUTE_MIN_DATA_VALUE = ‐10000; 128 const FFT_MANIPULATION_TYPE = 'fft'; 63 const DEFAULT_DATA_POINT = 0; 129 const MANIPULATION_TYPE_LIST = {}; 64 const MAX_DATA_AMOUNT = 2000;

booter.js

49


130 MANIPULATION_TYPE_LIST[MOVING_AVERAGE_MANIPULATION_TYPE] = 'moving average'; 131 MANIPULATION_TYPE_LIST[DIFFERENTIAL_MANIPULATION_TYPE] = 'differential'; 132 MANIPULATION_TYPE_LIST[INVERSE_MANIPULATION_TYPE] = 'inverse'; 133 MANIPULATION_TYPE_LIST[THRESHOLD_MANIPULATION_TYPE] = 'threshold'; 134 MANIPULATION_TYPE_LIST[FFT_MANIPULATION_TYPE] = 'FFT'; 135 136 /** 137 * Electronics constants 138 */ 139 const MAX_DATA_VALUE_VOLTAGE = 5.56; 140 141 /** 142 * Loaded scripts 143 */ 144 var scriptPathArr = [ 145 PATH_TO_VENDOR +'class.min.js', 146 PATH_TO_VENDOR +'underscore.min.js', 147 PATH_TO_VENDOR +'jquery.min.js', 148 PATH_TO_VENDOR +'jquery.requestAnimationFrame.js', 149 //PATH_TO_VENDOR +'less.min.js', 150 PATH_TO_VENDOR +'d3.min.js', 151 PATH_TO_VENDOR +'d3.layout.min.js', 152 PATH_TO_VENDOR +'three.min.js', 153 PATH_TO_VENDOR +'three.canvasrenderer.js', 154 PATH_TO_VENDOR +'three.projector.js', 155 PATH_TO_VENDOR +'zoomooz.min.js', 156 PATH_TO_VENDOR +'tween.js', 157 PATH_TO_VENDOR +'angular.min.js', 158 PATH_TO_VENDOR +'angular‐animate.min.js', 159 PATH_TO_VENDOR +'angular‐aria.min.js', 160 PATH_TO_VENDOR +'angular‐animate.min.js', 161 PATH_TO_VENDOR +'angular‐material.min.js', 162 PATH_TO_VENDOR +'angular‐dragula.min.js', 163 PATH_TO_VENDOR +'tangle.js', 164 PATH_TO_VENDOR +'hammer.min.js', 165 //PATH_TO_VENDOR +'backbone.js', 166 //PATH_TO_VENDOR +'jquery‐ui.min.js', 167 //PATH_TO_VENDOR +'meatglue.js', 168 PATH_TO_UI +'ProtoProbesModule.js', 169 PATH_TO_UI +'ChannelListController.js', 170 PATH_TO_UI +'ChannelController.js', 171 PATH_TO_UTILITIES +'Helpers.js', 172 PATH_TO_UTILITIES +'Storage.js', 173 PATH_TO_SCRIPTS +'Engine.js', 174 PATH_TO_MANAGERS +'Manager.js', 175 PATH_TO_MANAGERS +'SerialManager.js', 176 PATH_TO_GRAPHICS +'Graphic.js', 177 PATH_TO_GRAPHICS +'D3Graphic.js', 178 PATH_TO_GRAPHICS +'ThreeGraphic.js', 179 PATH_TO_GRAPHICS +'LineGraphic.js', 180 PATH_TO_GRAPHICS +'CircleGraphic.js', 181 PATH_TO_GRAPHICS +'SphereGraphic.js', 182 PATH_TO_GRAPHICS +'ComplexSphereGraphic.js', 183 PATH_TO_GRAPHICS +'PointCloudGraphic.js', 184 PATH_TO_GRAPHICS +'DisabledGraphic.js', 185 PATH_TO_GRAPHICS +'VectorGraphic.js', 186 PATH_TO_MANAGERS +'GraphicManager.js', 187 PATH_TO_DATA +'DataChannel.js', 188 PATH_TO_MANAGERS +'DataManager.js', 189 PATH_TO_MANIPULATIONS +'DataManipulation.js', 190 PATH_TO_MANIPULATIONS +'DifferentialManipulation.js', 191 PATH_TO_MANIPULATIONS +'IntegralManipulation.js', 192 PATH_TO_MANIPULATIONS +'MovingAverageManipulation.js', 193 PATH_TO_MANIPULATIONS +'InverseManipulation.js', 194 PATH_TO_MANIPULATIONS +'ThresholdManipulation.js',

booter.js

195 PATH_TO_MANAGERS +'InteractionManager.js', 196 ]; 197 198 /** 199 * Engine instance 200 */ 201 var engine; 202 203 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 204 /** BOOT 205 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 206 207 /** 208 * Booting of the application 209 */ 210 var bootApp = function() { 211 212 // debug 213 debug('Booting application (version '+ SYSTEM_VERSION +')...'); 214 215 // go to full screen 216 chrome.app.window.current().fullscreen(); 217 218 // load all the required scripts 219 requireScripts(function() { 220 221 // initialize the engine when all the scripts are loaded and 222 // the document is ready 223 $(document).ready(function() { 224 initializeEngine(); 225 }); 226 }); 227 }; 228 229 /** 230 * Shutting down of the application 231 */ 232 var destructApp = function() { 233 234 }; 235 236 /** 237 * Initialize the Engine class 238 */ 239 var initializeEngine = function() { 240 241 // debug 242 debug('Starting the Engine...'); 243 244 // create a new engine instance 245 engine = new Engine(); 246 247 // initialize the engine now the instance is known 248 engine.initializeApp(); 249 250 // debug 251 debug('The Engine is running!'); 252 }; 253 254 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 255 /** GETTERS 256 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 257 258 /** 259 * Get the engine instance

50


51

260 */ 325 } 261 var getEngine = function() { 326 327 // load the next script 262 return engine; 328 loadSingleScript(index + 1); 263 }; 329 }; 264 330 265 /** 331 // then bind the event to the callback function. 266 * Get the channel list controller 332 // there are several events for cross browser compatibility. 267 */ 333 script.onreadystatechange = callback; 268 var getChannelListController = function() { 334 script.onload = callback; 269 return angular.element($('.channel‐container')).scope(); 335 270 }; 336 // fire the loading 271 337 head.appendChild(script); 272 /** 338 }; 273 * Get the width of the screen 339 274 */ 340 // now start with the first file 275 var getScreenWidth = function() { 341 loadSingleScript(0); 276 return window.screen.width; 342 }; 277 }; 343 278 344 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 279 /** 345 /** DEBUGGING 280 * Get the height of the screen 346 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 281 */ 347 282 var getScreenHeight = function() { 348 /** 283 return window.screen.height; 349 * Debug 284 }; 350 */ 285 351 var debug = function(message, identifier, type) { 286 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 352 287 /** DEPENDENCIES **/ 353 // guard: check if the console exists 288 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 354 if (typeof console != 'object' || !DEBUG) { 289 355 return; 290 /** 356 } 291 * Loading all the dependencies 357 292 */ 358 // check if we need to add a seperator for the identifier 293 var requireScripts = function(onCompleteCallback) { 359 if (identifier !== undefined) { 294 360 identifier = ' ('+ identifier +')'; 295 // debug 361 } else { 296 debug('Loading scripts...'); 362 identifier = ''; 297 363 } 298 // count the scripts 364 299 var scriptCount = scriptPathArr.length; 365 // check the type 300 366 if (type === undefined) { 301 // loop all the scripts 367 type = 'DEBUG'; 302 var loadSingleScript = function(index) { 368 } 303 369 304 // variables 370 // log the message, prefix depends on the type 305 var isLastScript = (index == scriptCount ‐ 1); 371 if (typeof message === 'string') { 306 var scriptPath = scriptPathArr[index]; 372 console.log(SYSTEM_NAME + identifier +' ['+ type +']: ' + message); 307 var head = document.getElementsByTagName('head')[0]; 373 } else { 308 var script = document.createElement('script'); 374 console.log(message); 309 script.type = 'text/javascript'; 375 } 310 script.src = scriptPath + (DISABLE_CACHE ? '?d='+ Date.now() : ''); 376 }; 311 script.async = false; 377 312 378 /** 313 // set the callback of loading requireJS library 379 * Show an error message 314 var callback = function() { 380 */ 315 381 var error = function(message, identifier) { 316 // guard: check if this was the final script 382 debug(message, identifier, 'ERROR'); 317 if (isLastScript) { 383 }; 318 384 319 // debug 385 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 320 debug('All the scripts are loaded!'); 386 /** BOOT 321 387 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 322 // execute complete callback 388 323 onCompleteCallback(); 389 // finally boot the app! 324 return;

booter.js


1 /** 2 * The Engine class handles all other instances managing the application 3 */ 4 var Engine = Class.create({ 5 6 /** 7 * Class ID 8 */ 9 id: 'Engine', 10 11 /** 12 * Manager instances 13 */ 14 serialManager: null, 15 graphicManager: null, 16 interactionManager: null, 17 dataManager: null, 18 19 /** 20 * Helper instances 21 */ 22 storage: null, 23 24 /** 25 * Initialize 26 */ 27 initialize: function () { 28 // empty 29 }, 30 31 /** 32 * Initialize the manager instances 33 */ 34 initializeApp: function () { 35 36 // create new storage object 37 this.storage = new Storage(); 38 39 // create a new serial manager 40 this.serialManager = new SerialManager(); 41 42 // create a new interaction manager 43 this.dataManager = new DataManager(); 44 45 // create a new graphic manager 46 this.graphicManager = new GraphicManager(); 47 48 // create a new interaction manager 49 this.interactionManager = new InteractionManager(); 50 51 // perform other initialization sequences 52 this.graphicManager.initializeDummies(); 53 }, 54 55 /** 56 * Get the storage 57 */ 58 getStorage : function() { 59 return this.storage; 60 }, 61 62 /** 63 * Get the serial manager 64 */

engine.js

65 getSerialManager : function() { 66 return this.serialManager; 67 }, 68 69 /** 70 * Get the graphic manager 71 */ 72 getGraphicManager : function() { 73 return this.graphicManager; 74 }, 75 76 /** 77 * Get the interaction manager 78 */ 79 getInteractionManager : function() { 80 return this.interactionManager; 81 }, 82 83 /** 84 * Get the data manager 85 */ 86 getDataManager : function() { 87 return this.dataManager; 88 }, 89 90 /** 91 * Get the angular controller handling all the channels 92 */ 93 getChannelListController : function() { 94 return angular.element($('.channel‐container')).scope(); 95 }, 96 97 /** 98 * Get the controller for a specific channel 99 */ 100 getChannelController : function(channelId) { 101 return angular.element($('.channel[data="'+ channelId +'"] .channel__controller')).scope(); 102 }, 103 104 });

52


1 /** 2 * The GraphicManager class handles all data visualisations 3 */ 4 var Manager = Class.create({ 5 6 /** 7 * Class ID 8 */ 9 id: 'Manager', 10 11 /** 12 * Variables 13 */ 14 parent : null, 15 thread : null, 16 threadDelay : 0, 17 running : false, 18 animationFrame : false, 19 20 /** 21 * Initialize 22 */ 23 initialize: function (threadDelay, animationFrame) { 24 25 // initialize variables 26 this.parent = this; 27 28 // initialize the thread 29 this.initializeThread(threadDelay, animationFrame); 30 }, 31 32 /** 33 * Initialize 34 */ 35 initializeThread: function (threadDelay, animationFrame) { 36 37 // set animation frame 38 this.animationFrame = animationFrame; 39 40 // set the thread delay 41 this.threadDelay = threadDelay; 42 43 // start the Thread 44 this.running = true; 45 46 // set standard interval when not running on the animation frames 47 if (this.animationFrame !== true) { 48 this.thread = setIntervalWithContext(this.__run, this.threadDelay, this); 49 } 50 51 // execute the first run 52 this.__run(); 53 }, 54 55 /** 56 * Thread pre‐execution 57 */ 58 __run: function () { 59 60 // check for new animation frame 61 if (this.animationFrame) { 62 this.thread = requestAnimationFrame(this.__run.bind(this)); 63 } 64

manager.js

65 // guard: check for active 66 if (!this.running) { 67 return; 68 } 69 70 // execute 71 this.run(); 72 }, 73 74 /** 75 * Thread execution 76 */ 77 run: function () { 78 // implemented in sub class 79 }, 80 81 /** 82 * Toggle the running of the thread 83 */ 84 toggleRunning: function () { 85 this.running = !this.running; 86 }, 87 88 /** 89 * Set the running of the thread 90 */ 91 setRunning: function (running) { 92 this.running = running; 93 }, 94 95 /** 96 * Set the running of the thread 97 */ 98 isRunning: function () { 99 return this.running; 100 }, 101 102 /** 103 * Get the thread delay 104 */ 105 getThreadDelay: function () { 106 return this.threadDelay; 107 }, 108 109 /** 110 * Destruct method 111 */ 112 destruct: function () { 113 114 // clear the interval 115 clearInterval(this.thread); 116 } 117 });

53


1 /** 2 * The Interaction Manager handles all the user actions 3 */ 4 var InteractionManager = Class.create(Manager, { 5 6 /** 7 * Class ID 8 */ 9 id: 'InteractionManager', 10 11 /** 12 * A list of all the available data manipulations 13 */ 14 manipulations: [ 15 { 'classId': DifferentialManipulation.id, 'shortName' : DifferentialManipulation.shortName }, 16 ], 17 18 /** 19 * Initialize 20 */ 21 initialize: function ($super) { 22 23 // initialize the manager 24 $super(1000); 25 26 // initialize touch 27 this.initializeTouch(); 28 29 // initialize 30 this.initializeEvents(); 31 }, 32 33 /** 34 * Execution of the thread 35 */ 36 run : function() { 37 38 }, 39 40 /** 41 * Initialize as touch screen when enabled 42 */ 43 initializeTouch : function() { 44 45 // guard: check if the touch screen is enabled 46 if (!TOUCH_SCREEN_MODE) { 47 return; 48 } 49 50 // SOURCE: http://stackoverflow.com/questions/24065357/disable‐the‐context‐menu‐in‐chrome‐apps 51 document.addEventListener('contextmenu', function(e) { 52 e.preventDefault(); 53 }); 54 }, 55 56 /** 57 * Initialize the DOM events 58 */ 59 initializeEvents : function() { 60 61 // handle the connect button 62 $('.connect‐port‐button').click(function() { 63 64 // variables

interactionmanager.js

65 var portPath = $('.port‐list').val(); 66 67 // guard: check if the port path is valid 68 if (_.isEmpty(portPath)) { 69 return; 70 } 71 72 // connect to the port 73 getEngine().getSerialManager().connect(portPath); 74 }); 75 76 // handle the differentiate toggle 77 $('.switch input').change(function(event) { 78 79 // variables 80 var manipulationId = $(this).attr('data'); 81 var currentValue = $(this).is(':checked'); 82 var isActive = (currentValue == 1 ? true : false); 83 84 // set the active flag 85 getEngine().getGraphicManager().setManipulationState(manipulationId, isActive); 86 87 // blur 88 $(this).blur(); 89 }); 90 91 // key events 92 $('body').keyup(function(event) { 93 94 // guard: check for space bar 95 if (event.keyCode == 32) { 96 97 // toggle the running of the graphic manager 98 getEngine().getGraphicManager().toggleRunning(); 99 return; 100 } 101 102 // guard: check for to sphere button ('s') 103 if (event.keyCode == 83) { 104 getEngine().getGraphicManager().setGraphicTypes(SPHERE_GRAPHIC_TYPE); 105 return; 106 } 107 108 // guard: check for to circle button ('c') 109 if (event.keyCode == 67) { 110 getEngine().getGraphicManager().setGraphicTypes(CIRCLE_GRAPHIC_TYPE); 111 return; 112 } 113 114 // guard: check for to circle button ('l') 115 if (event.keyCode == 76) { 116 getEngine().getGraphicManager().setGraphicTypes(LINE_GRAPHIC_TYPE); 117 return; 118 } 119 120 // guard: check for to shape button ('h') 121 if (event.keyCode == 72) { 122 getEngine().getGraphicManager().setGraphicTypes(SHAPE_GRAPHIC_TYPE); 123 return; 124 } 125 126 // guard: check for to point cloud button ('p') 127 if (event.keyCode == 80) { 128 getEngine().getGraphicManager().setGraphicTypes(POINT_CLOUD_GRAPHIC_TYPE); 129 return;

54


1 /** 2 * The GraphicManager class handles all data visualisations 3 */ 4 var GraphicManager = Class.create(Manager, { 5 6 /** 7 * Class ID 8 */ 9 id: 'GraphicManager', 10 11 /** 12 * Storage for all the graphics 13 */ 14 graphics: {}, 15 16 /** 17 * Initialize 18 */ 19 initialize: function ($super) { 20 21 // enable dark mode 22 //$('body').addClass('dark'); 23 //$('body').addClass('hide‐controls'); 24 25 // initialize the super class 26 $super(1000 / FRAME_RATE, true); 27 }, 28 29 /** 30 * Initialize the dummy charts 31 */ 32 initializeDummies : function() { 33 34 // variables 35 var enableDummies = true; 36 var dummyAmount = 4; 37 38 // test with dummy data 39 for (var channelId = 1; channelId < 1 + dummyAmount && enableDummies; channelId++) { 40 41 // variables 42 var dataChannel = getEngine().getDataManager().addChannel(channelId + 1000, true); 43 } 44 45 // set interval 46 setInterval(function() { 47 48 // variables 49 var channels = getEngine().getDataManager().getChannels(); 50 var time = Date.now(); 51 52 // loop all the data channels 53 for (var channelId in channels) { 54 55 // guard: check if this is a dummy channel 56 if (channelId < 1000) { 57 continue; 58 } 59 60 // variables 61 var dataChannel = channels[channelId]; 62 var x = time / 1000; 63 var y = ((Math.sin(x) * Math.cos(2 * x) * Math.sin(5 * x) * Math.cos(10 * x)) * (MAX_DATA_VALUE / 64 var x2 = time / 500;

graphicmanager.js

65 var y2 = ((Math.sin(x2) * Math.sin(2 * x2) * Math.sin(5 * x2) * Math.cos(10 * x2)) * (MAX_DATA_ 66 var x3 = time / 100; 67 var y3 = ((Math.sin(x) * Math.sin(2 * x) * Math.sin(5 * x) * Math.sin(10 * x)) * (MAX_DATA_VALU 68 69 // add data to each channel 70 if (channelId <= 1001) { 71 dataChannel.addData([1, time, y]); 72 //dataChannel.addData([2, time, y2]); 73 } else if (channelId == 1002) { 74 dataChannel.addData([1, time, y2]); 75 } else { 76 dataChannel.addData([1, time, y3]); 77 dataChannel.addData([2, time, y3 + 100]); 78 dataChannel.addData([3, time, y3 + 200]); 79 } 80 } 81 82 //_dataChannel.addData([1, Date.now(), (Math.sin(x) * (MAX_DATA_VALUE / 2) + (MAX_DATA_VALUE / 2) 83 //_dataChannel.addData([1, Date.now(), (5 * (0.0572 * Math.cos(4.667 * x) + 0.0218 * Math.cos(12. 84 }, 50); 85 }, 86 87 /** 88 * Execution of the thread 89 */ 90 run : function() { 91 92 // perform tween update 93 TWEEN.update(); 94 95 // loop all the data channels 96 for (var graphicId in this.graphics) { 97 98 // get the graphic 99 var graphic = this.graphics[graphicId]; 100 101 // render the graphic 102 graphic.render(); 103 } 104 }, 105 106 /** 107 * Add a new graphic 108 */ 109 addGraphic : function(dataChannel, graphicRenderer, graphicType) { 110 111 // guard: check if the graphic is valid 112 if (_.isEmpty(dataChannel)) { 113 return false; 114 } 115 116 // variables 117 var graphicId = Object.size(this.graphics); 118 var graphic = this.createGraphicInstance(dataChannel, graphicId, graphicRenderer, graphicType); 119 120 // guard: check if the graphic is valid 121 if (_.isEmpty(graphic)) { 122 error('An invalid graphic has been created for the channel: '+ dataChannel.getChannelId()); 123 return false; 124 } 125 126 // add the graphic to the array 127 this.graphics[graphicId] = graphic; 128 129 // debug

55


195 break; 196 197 // point cloud graphic 198 case POINT_CLOUD_GRAPHIC_TYPE: 199 graphic = new PointCloudGraphic(dataChannel, { 200 'graphicId' : graphicId, 201 'graphicType' : graphicType, 202 }); 203 break; 204 205 // vector graphic 206 case VECTOR_GRAPHIC_TYPE: 207 graphic = new VectorGraphic(dataChannel, { 208 'graphicId' : graphicId, 209 'graphicType' : graphicType, 210 }); 211 break; 212 213 // disabled graphic 214 case DISABLED_GRAPHIC_TYPE: 215 graphic = new DisabledGraphic(dataChannel, { 216 'graphicId' : graphicId, 217 'graphicType' : graphicType, 218 }); 219 break; 220 } 221 222 return graphic; 223 }, 224 225 /** 226 * Redraw all the graphics 227 * NOTE: only needed when there are major manipulations 228 */ 229 redraw : function() { 230 231 // loop all the graphics 232 for (var graphicId in this.graphics) { 233 234 // variables 235 var graphic = this.graphics[graphicId]; 236 237 // redraw each Graphic 238 graphic.redraw(); 239 } 240 }, 241 242 /** 243 * Set the differentiate flag 244 */ 245 setManipulationState: function(manipulationId, active) { 246 247 // debug 248 debug('The manipulation '+ manipulationId +' has been set to: '+ active); 249 250 // loop all the graphics 251 for (var graphicId in this.graphics) { 252 253 // variables 254 var graphic = this.graphics[graphicId]; 255 var manipulation = graphic.getManipulation(manipulationId); 256 257 // guard: check if the manipulation is valid 258 if (_.isEmpty(manipulation)) { 259 continue;

graphicmanager.js

260 } 261 262 // enable or disable the manipulation 263 manipulation.setActive(active); 264 265 // request redraw 266 graphic.redraw(); 267 } 268 }, 269 270 /** 271 * Set the graphic type of all graphics at once 272 */ 273 setGraphicTypes : function(graphicType) { 274 275 // switch the graphic type of all graphics 276 for (var graphicId in this.graphics) { 277 278 /// variables 279 var graphic = this.graphics[graphicId]; 280 281 // set the type to something else 282 graphic.setGraphicType(graphicType); 283 } 284 }, 285 286 /** 287 * Get the container where all the graphics are rendered in 288 */ 289 getGraphicContainer : function(graphic, channelId, rawElement) { 290 291 // variables 292 var container = (graphic instanceof LineGraphic) ? $('.channel__graph[data="'+ channelId + 293 294 // return the container 295 return rawElement === true ? container[0] : container; 296 }, 297 298 /** 299 * Get the container where all the graphics are rendered in 300 */ 301 getGraphicByChannelId : function(channelId) { 302 303 // loop all the graphics 304 for (var graphicId in this.graphics) { 305 306 // variables 307 var graphic = this.graphics[graphicId]; 308 309 // guard: make sure this graphic is a line graphic instance 310 if (!(graphic instanceof LineGraphic)) { 311 continue; 312 } 313 314 // guard: check for a match 315 if (graphic.dataChannelId == channelId) { 316 return graphic; 317 } 318 } 319 320 return undefined; 321 }, 322 323 /** 324 * Get the view container where all the graphics are rendered in

56


325 */ 326 getViewGraphicByChannelId : function(channelId) { 327 328 // loop all the graphics 329 for (var graphicId in this.graphics) { 330 331 // variables 332 var graphic = this.graphics[graphicId]; 333 334 // guard: check if this is not a line graphic 335 if (graphic instanceof LineGraphic) { 336 continue; 337 } 338 339 // guard: check for a match 340 if (graphic.dataChannelId == channelId) { 341 return graphic; 342 } 343 } 344 345 return undefined; 346 }, 347 348 /** 349 * Get all the current graphics 350 */ 351 getGraphics : function() { 352 return this.graphics; 353 }, 354 });

graphicmanager.js

57


1 /** 2 * Import the Chrome Serial instance 3 */ 4 const chromeSerial = chrome.serial; 5 6 /** 7 * SerialManager class that handles all peripheral devices 8 */ 9 var SerialManager = Class.create(Manager, { 10 11 /** 12 * Class ID 13 */ 14 id: 'SerialManager', 15 16 /** 17 * Constants 18 */ 19 AUTO_CONNECT_PORT_ENABLED : true, 20 AUTO_CONNECT_PORT : 'COM4', 21 22 /** 23 * Variables 24 */ 25 channelId : 0, 26 portPath : '', 27 lastConnectionPortPath : '', 28 connectionId : ‐1, 29 connectionPortPath: '', 30 connectionInfo: {}, 31 dataBuffer : '', 32 33 /** 34 * Initialize 35 */ 36 initialize: function ($super) { 37 38 // initialize the super class 39 $super(1000); 40 41 // initialize the channel 42 this.channelId = generateUniqueId(); 43 44 // fetch the last connection port path 45 // this will enable us to automatically connect 46 getEngine().getStorage().getLastPortPath(function (result) { 47 48 // guard: check if the result is valid 49 if (_.isEmpty(result)) { 50 return; 51 } 52 53 // debug 54 debug('A previous connected com port has been found: '); 55 }); 56 }, 57 58 /** 59 * Execution of the thread 60 */ 61 run : function() { 62 63 // update the port list 64 this.updatePortList();

serialmanager.js

65 }, 66 67 /** 68 * Update the available devices list 69 */ 70 updatePortList : function() { 71 72 // variables 73 var thisObject = this; 74 75 // populate the list of available devices 76 this.getDevices(function(ports) { 77 78 // get dropdown port selector 79 var portList = $('.port‐list'); 80 var currentValue = $(portList).val(); 81 82 // clear existing options 83 $(portList).html(''); 84 85 // add new options 86 ports.forEach(function (port) { 87 88 // variables 89 var displayName = port.path; 90 var selectedClause = ''; 91 92 // check if the name is valid 93 if (!displayName) { 94 displayName = 'unknown'; 95 } 96 97 // check for selected clause 98 if (!currentValue) { 99 currentValue = port.path; 100 } 101 102 // check for auto connect 103 if (thisObject.AUTO_CONNECT_PORT_ENABLED && port.path == thisObject.AUTO_CONNECT_PORT && !thisO 104 thisObject.connect(port.path); 105 } 106 107 // create a new option element 108 var newOption = '<option value="'+ port.path +'">'+ displayName +'</option>'; 109 110 // add the new option element 111 $(portList).append(newOption); 112 }); 113 114 // set the port list value to the old one 115 $(portList).val(currentValue); 116 }); 117 }, 118 119 /** 120 * On receive data 121 */ 122 onReceive : function(receiveInfo) { 123 124 // guard: check if the connection matches the received data connection 125 if (receiveInfo.connectionId !== this.connectionId) { 126 return; 127 } 128 129 // add to the buffer

58


130 this.dataBuffer += byteArrayToString(receiveInfo.data); 131 132 // debug 133 //debug('New data buffer length: '+ this.dataBuffer.length); 134 135 // variables 136 var packetStartLength = PACKET_START.length; 137 var packetEndLength = PACKET_END.length; 138 var packetStartIndex = this.dataBuffer.indexOf(PACKET_START); 139 var packetEndIndex = this.dataBuffer.indexOf(PACKET_END, packetStartIndex); 140 141 // loop until there are no packets in the buffer anymore 142 while (packetStartIndex >= 0 && packetEndIndex >= 0) { 143 144 // get the first packet found in the buffer 145 var rawPacket = this.dataBuffer.substring(packetStartIndex + packetStartLength, packetEndIndex); 146 var rawPacketArr = stringToByteArray(rawPacket); 147 148 // remove the packet from the buffer 149 this.dataBuffer = this.dataBuffer.substring(packetEndIndex + packetEndLength + 1, this 150 151 // potentially look for new packets 152 packetStartIndex = this.dataBuffer.indexOf(PACKET_START); 153 packetEndIndex = this.dataBuffer.indexOf(PACKET_END); 154 155 // variables 156 var channelId = parseInt(rawPacketArr.shift()); 157 var packetId = parseInt(rawPacketArr.shift()); 158 var rawDataArr = rawPacketArr; 159 var dataArr = []; 160 161 // loop all the data 162 for (var index in rawDataArr) { 163 164 // variables 165 var rawData = rawDataArr[index]; 166 167 // convert each data piece to an integer 168 dataArr.push(parseInt(rawData)); 169 } 170 171 // debug 172 /*debug('‐‐‐‐‐‐‐‐‐‐‐‐‐'); 173 debug(rawPacketArr); 174 debug(channelId); 175 debug(packetId); 176 debug(dataArr);*/ 177 178 // guard: check if the channel and packet ID are set 179 if (isNaN(channelId) || isNaN(packetId) || channelId <= 0 || packetId <= 0) { 180 continue; 181 } 182 183 // finally handle the packet 184 this.handlePacket(channelId, packetId, dataArr); 185 } 186 }, 187 188 /** 189 * Event when an error is received 190 */ 191 onReceiveError : function(errorInfo) { 192 193 // guard: check if the connection matches the current connection 194 if (errorInfo.connectionId !== this.connectionId) {

serialmanager.js

195 return; 196 } 197 198 // variables 199 var thisObject = this; 200 201 // show the error 202 console.log(errorInfo); 203 204 // reboot serial 205 thisObject.destruct(); 206 207 // give some time to deconnect the serial 208 setTimeout(function() { 209 210 // reconnect 211 thisObject.connect(thisObject.portPath); 212 }, 200); 213 }, 214 215 /** 216 * Handler for received packets 217 */ 218 handlePacket : function(channelId, packetId, rawDataArr) { 219 220 // debug 221 //debug('Handling a new packet (ID: '+ packetId + ') for channel (ID: '+ channelId +'):'); 222 //debug(rawDataArr); 223 224 // check for a data packet 225 if (packetId == DATA_POINT_PACKET_ID) { 226 227 // send the packet to the graphic manager 228 getEngine().getDataManager().handleData(channelId, rawDataArr); 229 } else if (packetId == MEASUREMENT_TYPE_PACKET_ID) { 230 231 // variables 232 var channel = getEngine().getDataManager().getChannel(channelId); 233 var measurementTypeId = rawDataArr[0] == DIGITAL_MEASUREMENT_TYPE_ID ? DIGITAL_MEASUREMENT_TYPE_I 234 235 // guard: check if the channel is valid 236 if (_.isEmpty(channel)) { 237 return; 238 } 239 240 // set to the new type 241 channel.setMeasurmentType(measurementTypeId); 242 } 243 }, 244 245 /** 246 * Get all the available devices 247 */ 248 getDevices : function(callback) { 249 chromeSerial.getDevices(callback); 250 }, 251 252 /** 253 * Connect to a specific device 254 */ 255 connect : function(portPath) { 256 257 // variables 258 var thisObject = this; 259 var options = {

59


260 'bitrate': SERIAL_BAUD_RATE, 261 }; 262 263 // set local parameters 264 this.portPath = portPath; 265 266 // attempt to connect 267 try { 268 269 // connect 270 chromeSerial.connect(portPath, options, function(connectionInfo) { 271 272 // guard: check if the connection is valid 273 if (!connectionInfo) { 274 275 // debug error 276 debug('Failed to establish a serial connection with '+ portPath); 277 278 // attempt to connect again 279 thisObject.connect(thisObject.portPath); 280 return; 281 } 282 283 // debug 284 debug('Successfully connected to '+ portPath); 285 286 // initialize variables 287 thisObject.connectionInfo = connectionInfo; 288 thisObject.connectionPortPath = portPath; 289 thisObject.connectionId = connectionInfo.connectionId; 290 291 // add listeners 292 chromeSerial.onReceive.addListener(thisObject.onReceive.bind(thisObject)); 293 chromeSerial.onReceiveError.addListener(thisObject.onReceiveError.bind(thisObject)); 294 295 // save the connection com port 296 thisObject.saveConnection(); 297 298 // remove the connection drop‐down 299 $('.port‐container').hide(); 300 }); 301 } catch (error) { 302 // empty 303 } 304 }, 305 306 /** 307 * Save this connection to the storage 308 */ 309 saveConnection: function() { 310 311 // save the current connected com port 312 getEngine().getStorage().setLastPortPath(this.connectionPortPath); 313 }, 314 315 /** 316 * Send data to the device 317 */ 318 send : function(message) { 319 320 // guard: check if the connection is valid 321 if (this.connectionId < 0) { 322 debug('Cannot send data to the device as the connection is invalid.'); 323 return; 324 }

serialmanager.js

325 326 // sen the data 327 chromeSerial.send(this.connectionId, formatStringToArrayBuffer(message), function() { 328 // empty 329 }); 330 }, 331 332 /** 333 * Flush the data of the serial 334 */ 335 flush : function() { 336 337 // execute the flush 338 chrome.serial.flush(this.connectionId, function() { 339 // empty 340 }); 341 }, 342 343 /** 344 * Check if we are connected 345 */ 346 isConnected : function() { 347 return this.connectionId > 0; 348 }, 349 350 /** 351 * Disconnect from the device 352 */ 353 destruct : function() { 354 355 // guard: check if the connection is valid 356 if (this.connectionId < 0) { 357 debug('Cannot disconnect the device as the connection is invalid.'); 358 return; 359 } 360 361 // disconnect 362 chromeSerial.disconnect(this.connectionId, function(connectionInfo) { 363 // empty 364 }); 365 } 366 367 });

60


1 /** 2 * The DataManager class handles all the data per channel 3 */ 4 var DataManager = Class.create(Manager, { 5 6 /** 7 * Class ID 8 */ 9 id: 'DataManager', 10 11 /** 12 * All the live channels 13 */ 14 channels : {}, 15 16 /** 17 * Initialize 18 */ 19 initialize: function ($super) { 20 21 // initialize the super class 22 $super(1000); 23 }, 24 25 /** 26 * Execution of the thread 27 */ 28 run : function() { 29 30 // loop all the channels 31 for (var channelId in this.channels) { 32 33 // variables 34 var dataChannel = this.getChannel(channelId); 35 var lastUpdateTime = dataChannel.getLastUpdateTime(); 36 var deltaTime = Date.now() ‐ lastUpdateTime; 37 38 // check if this channel is inactive for too long 39 if (deltaTime > DATA_CHANNEL_EXPIRY_TIME) { 40 41 // hide this channel 42 //dataChannel.disable(); 43 } else { 44 45 // show this channel 46 //dataChannel.enable(); 47 } 48 } 49 }, 50 51 /** 52 * Handle any new data from a channel 53 * A new channel is automatically generated when the channel is not known yet 54 * Or data is added to an existing channel 55 */ 56 handleData : function(channelId, rawDataArr) { 57 58 // check if the channel is already known 59 if (!this.channelExists(channelId)) { 60 this.addChannel(channelId); 61 } 62 63 // debug 64 //debug('New data added to channel: '+ channelId);

datamanager.js

65 66 // get the channel 67 var dataChannel = this.getChannel(channelId); 68 69 // add data to the channel 70 dataChannel.addData(rawDataArr); 71 }, 72 73 /** 74 * Check if a channel exists 75 */ 76 channelExists : function(channelId) { 77 return (channelId in this.channels); 78 }, 79 80 /** 81 * Add a new channel to the array 82 */ 83 addChannel : function(channelId, isDummy) { 84 85 // guard: check if this channel already exists 86 if (this.channelExists(channelId)) { 87 return null; 88 } 89 90 // debug 91 debug('Adding a new channel: '+ channelId); 92 93 // variables 94 var dataChannel = new DataChannel(channelId, isDummy); 95 96 // add the channel to the object 97 this.channels[channelId] = dataChannel; 98 99 // add the UI element 100 getEngine().getChannelListController().addChannel(channelId); 101 102 // add the graphic when the channel is added to the UI 103 setTimeout(function() { 104 getEngine().getGraphicManager().addGraphic(dataChannel, D3_GRAPHIC_RENDERER, LINE_GRAPHIC_TYPE); 105 getEngine().getGraphicManager().addGraphic(dataChannel, D3_GRAPHIC_RENDERER, DISABLED_GRAPHIC_TYP 106 }, ONE_SECOND * 0.2); 107 108 // return the new channel 109 return dataChannel; 110 }, 111 112 /** 113 * Remove a channel to the array 114 */ 115 removeChannel : function(channelId) { 116 117 // debug 118 debug('Removing a channel: '+ channelId); 119 120 // add the UI element 121 getEngine().getChannelListController().removeChannel(channelId); 122 123 // delete the channel 124 delete this.channels[channelId]; 125 }, 126 127 /** 128 * Get all the channels 129 */

61


130 getChannels : function() { 131 132 // return the channels 133 return this.channels; 134 }, 135 136 /** 137 * Get a channel by channel ID 138 */ 139 getChannel : function(channelId) { 140 141 // guard: check if this channel already exists 142 if (!this.channelExists(channelId)) { 143 return null; 144 } 145 146 // return the channel 147 return this.channels[channelId]; 148 } 149 });

datamanager.js

62


1 /** 2 * Channel Controller 3 */ 4 ProtoProbesApp.controller('ChannelController', ['$scope', '$mdDialog', 'dragulaService', function 5 6 /** 7 * Constants 8 */ 9 $scope.ALL_FLOWS_ACTIVE = false; 10 $scope.SENSOR_TYPE_LIST = SENSOR_TYPE_LIST; 11 $scope.VIEW_TYPE_LIST = VIEW_TYPE_LIST; 12 $scope.MANIPULATION_TYPE_LIST = MANIPULATION_TYPE_LIST; 13 14 /** 15 * Channel information 16 */ 17 $scope.channelId = 0; 18 $scope.channelItem = null; 19 $scope.channel = null; 20 $scope.latestDataPoints = [ 0 ]; 21 22 /** 23 * Graphic instances 24 */ 25 $scope.graphic = null; 26 $scope.viewGraphic = null; 27 $scope.viewType = DISABLED_GRAPHIC_TYPE; 28 29 /** 30 * Manipulation info 31 */ 32 $scope.manipulations = []; 33 34 /** 35 * Sensor types 36 */ 37 $scope.sensorTypeId = UNKNOWN_SENSOR_TYPE_ID; 38 39 /** 40 * Interactions 41 */ 42 $scope.lastPanAbsoluteDeltaX = 0; 43 $scope.lastPanAbsoluteDeltaY = 0; 44 45 /** 46 * Initialize 47 */ 48 $scope.initialize = function(channelId) { 49 50 // debug 51 debug('Initializing the Channel Controller with channel ID: '+ channelId, $scope.getName()); 52 53 // set the variables 54 $scope.channelId = channelId; 55 $scope.channelItem = getEngine().getChannelListController().getChannelItem(channelId); 56 $scope.channel = $scope.channelItem.channel; 57 58 // initialize dragula 59 $scope.initializeDragula(); 60 61 // update the colors after a bit of waiting 62 setTimeout(function() { 63 64 // get the graphic as it might now be initialized

name

65 $scope.graphic = getEngine().getGraphicManager().getGraphicByChannelId(channelId); 66 $scope.viewGraphic = getEngine().getGraphicManager().getViewGraphicByChannelId(channelId); 67 $scope.manipulations = $scope.graphic.manipulations; 68 $scope.viewGraphic.manipulations = $scope.manipulations; 69 70 // initialize the CSS colors 71 $scope.initializeColors(); 72 73 // initialize DOM events 74 $scope.initializeDOMEvents(); 75 76 // initialize dragula 77 $scope.initializeManipulations(); 78 79 // show this channel 80 $scope.showChannelItem(); 81 82 // reset the styles on the body to prevent weird zooming containers 83 $('body').attr('style', ''); 84 85 // TMP: fix progress bar top 86 $($scope.getChannelElement()).find('.measurement‐type__identifier').css('height', '100%' 87 setTimeout(function() { $($scope.getChannelElement()).find('.measurement‐type__identifier' 88 setTimeout(function() { $($scope.getChannelElement()).find('.measurement‐type__identifier' 89 setTimeout(function() { $($scope.getChannelElement()).find('.measurement‐type__identifier' 90 setTimeout(function() { $($scope.getChannelElement()).find('.measurement‐type__identifier' 91 92 // apply any changes 93 $scope.safeApply(); 94 95 }, ONE_SECOND * 0.5); 96 }; 97 98 /** 99 * Initialize the Dragula library 100 */ 101 $scope.initializeDragula = function() { 102 103 // variables 104 var containerName = 'channel‐manipulations‐'+ $scope.channelId; 105 106 // set options 107 dragulaService.options($scope, containerName, { 108 removeOnSpill: true, 109 moves: function (el, container, handle) { 110 return $(handle).hasClass('handle'); 111 } 112 }); 113 114 /** 115 * Dragula drop event 116 */ 117 $scope.$on(containerName +'.drop', function (e, el) { 118 $scope.updateManipulations(true); 119 }); 120 121 /** 122 * Dragula drop event 123 */ 124 $scope.$on(containerName +'.remove', function (e, el) { 125 $scope.updateManipulations(true); 126 }); 127 128 /** 129 * Dragula over event

63


130 */ 131 $scope.$on(containerName +'.over', function (e, el, container) { 132 // empty 133 }); 134 135 /** 136 * Dragula out event 137 */ 138 $scope.$on(containerName +'.out', function (e, el, container) { 139 // empty 140 }); 141 }; 142 143 /** 144 * Initialize the colors 145 */ 146 $scope.initializeColors = function() { 147 148 // variables 149 var color = $scope.channelItem.color; 150 var element = $scope.getChannelElement(); 151 152 // channel identifier color (top block) 153 $(element).find('.channel__identifier').css('background‐color', color); 154 155 // channel graph colors 156 $(element).find('svg path').css('fill', 'none'); 157 $(element).find('svg path').css('stroke', color); 158 $(element).find('svg circle.indicator').css('fill', color); 159 $(element).find('svg line').css('fill', color); 160 $(element).find('svg line').css('stroke', color); 161 }; 162 163 /** 164 * Set DOM events with jQuery 165 */ 166 $scope.initializeDOMEvents = function() { 167 168 // on document ready 169 $(document).ready(function() { 170 171 // variables 172 channelElement = $scope.getChannelElement(); 173 174 // initialize the zoom functionalities 175 channelElement.zoomTarget(); 176 177 // handle zoom clicks to make sure the inner content is set to active 178 $(channelElement).click(function(event) { 179 180 // call select 181 $scope.handleSelect(); 182 }); 183 184 // initialize side content buttons 185 $(channelElement).find('.channel__side‐button').click(function(event) { 186 187 // variables 188 var sideContent = $(this).parent(); 189 var centerContent = $(sideContent).siblings('.channel__center'); 190 var isOpen = $(sideContent).hasClass('open'); 191 192 // check to reset the center content when this is not the left button 193 if (!$(sideContent).is('.channel__side‐‐left')) { 194 centerContent = $();

channelcontroller.js

195 } 196 197 // check if this is a click triggered by the user 198 if (event.which) { 199 $(this).removeAttr('original‐content'); 200 } 201 202 // check the manipulation of the open flag 203 if (isOpen) { 204 $(sideContent).removeClass('open'); 205 $(centerContent).removeClass('small'); 206 } else { 207 $(sideContent).addClass('open'); 208 $(centerContent).addClass('small'); 209 } 210 211 // instantly defocus 212 $(this).blur(); 213 }); 214 215 // switch y‐axis ticks 216 $(channelElement).find('.y‐axis').on('click', function(event) { 217 $scope.toggleYAxisTicks(); 218 }); 219 220 // hide overlay 221 $(channelElement).find('.channel__overlay').on('click', function(event) { 222 $scope.hideOverlay(); 223 }); 224 225 // hide overlay not by children 226 $(channelElement).find('.channel__overlay *').on('click', function(event) { 227 event.stopPropagation(); 228 }); 229 230 // add manipulation button 231 $(channelElement).find('.select‐manipulation‐button').on('click', function(event) { 232 233 // prevent default action 234 event.preventDefault(); 235 236 // variables 237 var manipulationId = $(this).attr('data'); 238 239 // add the manipulation to the graphic 240 $scope.graphic.addManipulationById(manipulationId); 241 242 // update the UI 243 $scope.safeApply(function() { 244 245 // delay a bit for the manipulation to appear 246 setTimeout(function() { 247 248 // variables 249 var channelElement = $scope.getChannelElement(); 250 251 // handle pan on all manipulations 252 $(channelElement).find('.manipulations__parameter').each(function() { 253 254 // initialize adjusting manipulation parameters pan 255 var parameterElement = $(this); 256 var manipulationTouchHandler = new Hammer( 257 $(this)[0], 258 { direction: Hammer.DIRECTION_VERTICAL, threshold: ‐100 } 259 );

64


260 261 $(this).mousedown(function(event) { 262 var createTouchEvent = function(name, x, y, identifier) { 263 var event = document.createEvent('Event'); 264 event.initEvent('touch' + name, true, true); 265 266 event.touches = event.targetTouches = [{ 267 clientX: x, 268 clientY: y, 269 identifier: identifier || 0 270 }]; 271 272 //https://developer.mozilla.org/en‐US/docs/Web/API/TouchEvent.changedTouches 273 event.changedTouches = [{ 274 clientX: x, 275 clientY: y, 276 identifier: identifier || 0 277 }]; 278 279 return event; 280 }; 281 282 var dispatchTouchEvent = function(el, name, x, y, identifier) { 283 var event = createTouchEvent(name, x, y, identifier); 284 el.dispatchEvent(event); 285 }; 286 debug('faking hammer event!'); 287 dispatchTouchEvent($(this)[0], 'panmove', 50, 50); 288 }); 289 290 // handle any pans 291 manipulationTouchHandler.on('pan', function(event) { 292 293 // variables 294 var isFinal = event.isFinal; 295 var absoluteDeltaY = event.deltaY * ‐1; 296 var manipulationIndex = $(parameterElement).attr('data'); 297 var manipulation = $scope.getManipulationByIndex(manipulationIndex); 298 299 // guard: check if the manipulation is valid 300 if (_.isEmpty(manipulation)) { 301 return; 302 } 303 304 var stepSize = manipulation.getParameterStepSize(); 305 var delta = (absoluteDeltaY ‐ $scope.lastPanAbsoluteDeltaY) * stepSize; 306 var newParameterValue = manipulation.getParameter() + delta; 307 308 // update the last delta y 309 $scope.lastPanAbsoluteDeltaY = absoluteDeltaY; 310 311 // check for a final where we want to use the velocity to ease out 312 if (isFinal) { 313 314 // reset last absolute pan delta 315 $scope.lastPanAbsoluteDeltaY = 0; 316 } 317 318 // adjust the parameter 319 $scope.safeApply(function() { 320 321 // variables 322 var oldParameterValue = manipulation.getParameter(); 323 324 // update the parameter

channelcontroller.js

325 manipulation.setParameter(newParameterValue); 326 327 // guard: check if the value has changed 328 if (manipulation.getParameter() == oldParameterValue) { 329 return; 330 } 331 332 // redraw when the manipulation is active 333 if (manipulation.isActive()) { 334 $scope.graphic.redraw(true); 335 } 336 }); 337 }); 338 }); 339 340 // update the manipulations 341 $scope.updateManipulations(true); 342 }, ONE_SECOND * 0.1); 343 }); 344 345 // hide all overlays 346 $scope.hideOverlay(); 347 }); 348 349 // add manipulation button 350 $(channelElement).find('.select‐sensor‐button').on('click', function(event) { 351 352 // prevent default action 353 event.preventDefault(); 354 355 // hide all overlays 356 $scope.hideOverlay(); 357 }); 358 359 // add manipulation button 360 $(channelElement).find('.select‐view‐button').on('click', function(event) { 361 362 // prevent default action 363 event.preventDefault(); 364 365 // variables 366 var viewType = $(this).attr('data'); 367 368 // set the view type 369 $scope.setViewType(viewType); 370 371 // hide all overlays 372 $scope.hideOverlay(); 373 }); 374 375 // initialize the svg moving through time pan 376 var svgTouchHandler = new Hammer( 377 $(channelElement).find('svg')[0], 378 { direction: Hammer.DIRECTION_ALL, threshold: 0 } 379 ); 380 381 // handle any pans 382 svgTouchHandler.on('pan', function(event) { 383 384 // variables 385 var isFinal = event.isFinal; 386 var absoluteDeltaX = event.deltaX * ‐1; 387 var deltaX = (absoluteDeltaX ‐ $scope.lastPanAbsoluteDeltaX) * 14; 388 389 // update the last delta X

65


455 if (_.isEmpty(channelController)) { 390 $scope.lastPanAbsoluteDeltaX = absoluteDeltaX; 456 return; 391 457 } 392 // check for a final where we want to use the velocity to ease out 458 393 if (isFinal) { 459 // fire deselect event 394 460 channelController.handleDeselect(); 395 // reset last absolute pan delta 461 }); 396 $scope.lastPanAbsoluteDeltaX = 0; 462 397 463 // add class to this channel 398 // initialize a new tween 464 $(channelElement).addClass('selected'); 399 /*var tween = new TWEEN.Tween({ deltaX : event.velocityX * 1 }) 465 400 .to({ deltaX : 0 }, event.velocityX / 100) 466 // update manipulations 401 .onUpdate(function() { 467 setTimeout(function() { 402 //$scope.setPan(this.deltaX); 468 $scope.updateManipulations(false); 403 }).start();*/ 469 }, ONE_SECOND); 404 } 470 }; 405 471 406 // handle the pan 472 /** 407 $scope.setPan(deltaX); 473 * Handler when a channel is deselected 408 }); 474 */ 409 }); 475 $scope.handleDeselect = function() { 410 }; 476 411 477 // guard: check if this channel is already deselected 412 /** 478 if (!$scope.isSelected()) { 413 * Initialize the manipulations defined in the graphics object 479 return; 414 */ 480 } 415 $scope.initializeManipulations = function() { 481 416 482 // variables 417 // call initial update 483 var channelElement = $scope.getChannelElement(); 418 setTimeout(function() { 484 419 $scope.updateManipulations(false); 485 // remove selected class 420 }, ONE_SECOND); 486 $(channelElement).removeClass('selected'); 421 }; 487 422 488 // hide all overlays 423 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 489 $scope.hideOverlay(); 424 /** EVENTS **/ 490 }; 425 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 491 426 492 /** 427 /** 493 * Toggle a manipulation on or off 428 * Handler when this channel is selected 494 */ 429 */ 495 $scope.toggleManipulation = function(manipulationIndex) { 430 $scope.handleSelect = function() { 496 431 497 // variables 432 // guard: check if this channel is already selected 498 var manipulation = $scope.getManipulationByIndex(manipulationIndex); 433 if ($scope.isSelected()) { 499 434 return; 500 // guard: check if the manipulation is valid 435 } 501 if (_.isEmpty(manipulation)) { 436 502 return; 437 // variables 503 } 438 var channelElement = $scope.getChannelElement(); 504 439 505 // apply active state 440 // check if this channel was not selected yet 506 $scope.safeApply(function() { 441 if (!$(channelElement).hasClass('selected')) { 507 442 508 // adjust the manipulation in the graphic 443 // restore the panel states for this channel 509 manipulation.setActive(!manipulation.isActive()); 444 $scope.restoreSidePanels(); 510 445 } 511 // request update 446 512 $scope.updateManipulations(true); 447 // remove classes from all other elements 513 }); 448 $('.channel.selected').each(function() { 514 }; 449 515 450 // variables 516 /** 451 var channelId = $(this).attr('data'); 517 * Called when a manipulation has been changed 452 var channelController = getEngine().getChannelController(channelId); 518 */ 453 519 $scope.updateManipulations = function(redrawGraphic) { 454 // guard: check if the controller is valid

channelcontroller.js

66


520 521 // variables 522 var channelElement = $scope.getChannelElement(); 523 var list = $(channelElement).find('.manipulations__list'); 524 var lastItem = $(channelElement).find('.manipulations__list .manipulations__item').last(); 525 var addItem = $(channelElement).find('.add‐manipulation‐button'); 526 527 // reposition the add button 528 if (lastItem.length > 0) { 529 530 // variables 531 var lastItemTop = lastItem.position().top; 532 var lastItemLeft = lastItem.position().left; 533 534 // position with some margins 535 lastItemLeft += 13; 536 lastItemTop += 50 + 15; 537 538 // reposition the add button 539 $(addItem).css('left', lastItemLeft); 540 $(addItem).css('top', lastItemTop + 20); 541 } else { 542 $(addItem).css('left', $(list).css('padding‐left')); 543 $(addItem).css('top', 20); 544 } 545 546 // redraw the graphic when requested 547 if (redrawGraphic === true) { 548 $scope.graphic.redraw(); 549 } 550 }; 551 552 /** 553 * Update the view graphic 554 */ 555 $scope.setViewType = function(viewType) { 556 557 // guard: check if the view graphic is valid 558 if (_.isEmpty($scope.viewGraphic)) { 559 return; 560 } 561 562 // apply to UI 563 $scope.safeApply(function() { 564 565 // set the type 566 $scope.viewType = viewType; 567 568 // debug 569 debug('The graphic has been adjusted to a new view: '+ $scope.viewType); 570 571 // set the graphic type 572 $scope.viewGraphic.setGraphicType($scope.viewType); 573 $scope.viewGraphic = getEngine().getGraphicManager().getViewGraphicByChannelId($scope.channelId); 574 $scope.viewGraphic.manipulations = $scope.manipulations; 575 }); 576 }; 577 578 /** 579 * Handle a pan event 580 */ 581 $scope.setPan = function(deltaX) { 582 583 // variables 584 var currentStartX = $scope.graphic.xDomain[0];

channelcontroller.js

585 var currentEndX = $scope.graphic.xDomain[1]; 586 var newStartX = currentStartX + deltaX; 587 var newEndX = currentEndX + deltaX; 588 589 // check if the new start X is valid 590 if (newStartX < 0) { 591 newStartX = 0; 592 newEndX = currentEndX ‐ currentStartX; 593 } 594 595 // check if we should pause the graph 596 if (newStartX <= 1000) { 597 598 // execute unpause 599 if (!$scope.graphic.isRunning()) { 600 $scope.graphic.requestTransition(); 601 $scope.graphic.unpause(); 602 $scope.viewGraphic.unpause(); 603 } 604 } else { 605 $scope.graphic.pause(); 606 $scope.viewGraphic.pause(); 607 } 608 609 // guard: check for any changes 610 if (newStartX == currentStartX && newEndX == currentEndX) { 611 return; 612 } 613 614 // debug 615 //debug(event); 616 debug('Updating x‐axis scale to domain: '+ newStartX +' and '+ newEndX +'. For deltaX: ' 617 618 // update graphic 619 $scope.graphic.setXDomain(newStartX, newEndX); 620 $scope.graphic.updateXAxis(0); 621 $scope.graphic.redraw(false); 622 }; 623 624 /** 625 * Deselect this channel 626 */ 627 $scope.deselect = function() { 628 629 // zoom to viewport 630 $('.zoomContainer').click(); 631 }; 632 633 /** 634 * Show a specific overlay 635 */ 636 $scope.showOverlay = function(overlayClass) { 637 638 // variables 639 var channelElement = $scope.getChannelElement(); 640 var overlay = channelElement.find('.channel__overlay'); 641 var overlayContent = channelElement.find('.channel__overlay .'+ overlayClass); 642 var duration = ONE_SECOND * 0.2; 643 644 // first hide all current overlays 645 $scope.hideOverlay(0); 646 647 // guard: check if the content is valid 648 if (overlayContent.length <= 0) { 649 return;

67


68

650 } 715 /** 651 716 * Get the DOM element of the channel 652 $(overlay).focus(); 717 */ 653 718 $scope.getChannelElement = function() { 654 // show the overlay and the content 719 return getEngine().getChannelListController().getChannelElement($scope.channelId); 655 $(overlay).css('display', 'block'); 720 }; 656 $(overlay).css('opacity', 0); 721 657 $(overlayContent).css('display', 'block'); 722 /** 658 $(overlayContent).css('opacity', 0); 723 * Get the DOM element of a side panel 659 724 */ 660 // animate to show 725 $scope.getSidePanelElement = function(modifier) { 661 $(overlay).stop().animate({ 726 662 opacity : 1 727 // variables 663 }, duration); 728 var channelElement = $scope.getChannelElement(); 664 729 var selector = '.channel__side'; 665 // animate to show 730 666 $(overlayContent).stop().animate({ 731 // check if we should add a modifier 667 opacity : 1 732 if (!_.isEmpty(modifier)) { 668 }, duration); 733 selector += '‐‐'+ modifier; 669 }; 734 } 670 735 671 /** 736 return $(channelElement).find(selector); 672 * Hide all overlays 737 }; 673 */ 738 674 $scope.hideOverlay = function(duration) { 739 /** 675 740 * Get the manipulation by index 676 // variables 741 */ 677 var channelElement = $scope.getChannelElement(); 742 $scope.getManipulationByIndex = function(index) { 678 var overlay = channelElement.find('.channel__overlay'); 743 return $scope.manipulations[index]; 679 var overlayContent = channelElement.find('.channel__overlay‐content'); 744 }; 680 duration = duration === undefined ? ONE_SECOND * 0.2 : duration; 745 681 746 /** 682 // animate to show 747 * Get the DOM element of the channel 683 $(overlay).stop().animate({ 748 */ 684 opacity : 0 749 $scope.isSelected = function() { 685 }, duration, function() { 750 return $($scope.getChannelElement($scope.channelId)).hasClass('selected'); 686 $(overlay).css('display', 'none'); 751 }; 687 }); 752 688 753 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 689 // animate to show 754 /** SETTERS 690 $(overlayContent).stop().animate({ 755 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 691 opacity : 0 756 692 }, duration, function() { 757 /** 693 $(overlayContent).css('display', 'none'); 758 * Close the side panels 694 }); 759 */ 695 }; 760 $scope.closeSidePanels = function() { 696 761 $scope.closeSidePanel('left'); 697 /** 762 //$scope.closeSidePanel('right'); 698 * Switch between voltage and absolute readings 763 }; 699 */ 764 700 $scope.toggleYAxisTicks = function() { 765 /** 701 $scope.graphic.toggleYAxisTicks(); 766 * Close a single side panel 702 }; 767 */ 703 768 $scope.closeSidePanel = function(modifier, wasHumanInteraction) { 704 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 769 705 /** GETTERS **/ 770 // variables 706 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 771 var sidePanels = $scope.getSidePanelElement(modifier); 707 772 708 /** 773 // close by simulating the click event 709 * Get the data channel instance 774 sidePanels.each(function() { 710 */ 775 711 $scope.getChannel = function() { 776 // guard: check if this panel is already closed 712 return $scope.channel; 777 if (!$(this).is('.open')) { 713 }; 778 return; 714 779 }

channelcontroller.js


780 781 // remember the original state when not human 782 if (wasHumanInteraction !== true) { 783 $(this).attr('original‐state', 'open'); 784 } 785 786 // fake the click event 787 $(this).find('.channel__side‐button').click(); 788 }); 789 }; 790 791 /** 792 * Open the side panels 793 */ 794 $scope.openSidePanels = function() { 795 $scope.openSidePanel('left'); 796 $scope.openeSidePanel('right'); 797 }; 798 799 /** 800 * Open a single side panel 801 */ 802 $scope.openSidePanel = function(modifier, wasHumanInteraction) { 803 804 // variables 805 var sidePanels = $scope.getSidePanelElement(modifier); 806 807 // close by simulating the click event 808 sidePanels.each(function() { 809 810 // guard: check if this panel is already open 811 if ($(this).is('.open')) { 812 return; 813 } 814 815 // remember the original state when not human 816 if (wasHumanInteraction !== true) { 817 $(this).attr('original‐state', 'closed'); 818 } 819 820 // fake the click event 821 $(this).find('.channel__side‐button').click(); 822 }); 823 }; 824 825 /** 826 * Restore the side panels to their original state 827 */ 828 $scope.restoreSidePanels = function() { 829 830 // loop all the side panels 831 $scope.getChannelElement().find('.channel__side').each(function() { 832 833 // variables 834 var currentState = $(this).hasClass('open') ? 'open' : 'closed'; 835 var originalState = $(this).attr('original‐state'); 836 837 // check if there is an original state and if we should switch 838 if (_.isEmpty(originalState) || currentState == originalState) { 839 return; 840 } 841 842 // fake the click 843 $(this).find('.channel__side‐button').click(); 844 });

channelcontroller.js

845 }; 846 847 /** 848 * Show this channel 849 */ 850 $scope.showChannelItem = function() { 851 $($scope.getChannelElement()).css('opacity', 1); 852 }; 853 854 /** 855 * Hide this channel 856 */ 857 $scope.hideChannelItem = function() { 858 $($scope.getChannelElement()).css('opacity', 0); 859 }; 860 861 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 862 /** HELPERS 863 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 864 865 /** 866 * A safe apply helper 867 * SOURCE: http://stackoverflow.com/questions/22733422/angularjs‐rootscopeinprog‐inprogress‐error 868 */ 869 $scope.safeApply = function (fn) { 870 var phase = $scope.$root.$$phase; 871 if (phase == '$apply' || phase == '$digest') { 872 if (fn && typeof fn === 'function') { 873 fn(); 874 } 875 } else { 876 $scope.$apply(fn); 877 } 878 }; 879 880 /** 881 * Get the name of this controller 882 */ 883 $scope.getName = function () { 884 return 'channel‐controller'; 885 }; 886 887 /** 888 * An object key helper 889 * SOURCE: http://stackoverflow.com/questions/22691183/check‐object‐size‐in‐angularjs‐template‐doing‐ 890 */ 891 $scope.keys = Object.keys; 892 893 /** 894 * An object value helper 895 */ 896 $scope.values = Object.values; 897 898 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 899 /** ADMINISTRATIVE 900 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 901 902 /** 903 * Destroy this controller instance. 904 */ 905 $scope.destruct = function() { 906 // empty 907 }; 908 }]);

69


1 /** 2 * Channel Controller 3 */ 4 ProtoProbesApp.controller('ChannelListController', ['$scope', function($scope) { 5 6 /** 7 * Constants 8 */ 9 $scope.COLORS = [ 10 '#00796B', 11 '#FF9800', 12 //'#7B1FA2', 13 '#388E3C', 14 '#303F9F', 15 ]; 16 17 /** 18 * Storage for all the channels 19 */ 20 $scope.channelItems = {}; 21 22 /** 23 * Initialize 24 */ 25 $scope.initialize = function() { 26 27 // debug 28 debug('Initializing the Channel List Controller', $scope.getName()); 29 30 // initialize DOM events 31 $scope.initializeDOMEvents(); 32 33 // finally start running the thread 34 $scope.run(); 35 }; 36 37 /** 38 * Set DOM events with jQuery 39 */ 40 $scope.initializeDOMEvents = function() { 41 42 // on document ready 43 $(document).ready(function() { 44 45 // zoom to the viewport 46 $('.zoomViewport').zoomTo(); 47 48 // disable selected channels 49 $('.zoomViewport, .zoomContainer, md‐grid‐list').click(function(event) { 50 51 // loop all the channels 52 for (var channelId in $scope.channelItems) { 53 54 // get the channel 55 var channelController = getEngine().getChannelController(channelId); 56 57 // guard: check if the controller is valid 58 if (_.isEmpty(channelController)) { 59 continue; 60 } 61 62 // close all panels 63 channelController.closeSidePanels(); 64

channellistcontroller.js

65 // check if selected 66 channelController.handleDeselect(); 67 } 68 }); 69 }); 70 }; 71 72 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 73 /** THREAD 74 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 75 76 /** 77 * Thread run method 78 */ 79 $scope.run = function() { 80 81 // request new frame 82 requestAnimationFrame($scope.run); 83 }; 84 85 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 86 /** HANDLERS 87 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 88 89 /** 90 * Handle the addition of a new channel 91 */ 92 $scope.addChannel = function(channelId) { 93 94 // update UI 95 $scope.safeApply(function() { 96 97 // variables 98 var channel = getEngine().getDataManager().getChannel(channelId); 99 var color = $scope.getNewChannelColor(); 100 101 // check for no dummy with a fixed color 102 if (!channel.isDummy()) { 103 color = '#7B1FA2'; 104 } 105 106 // add the data channel to the array 107 $scope.channelItems[channelId] = { 108 'channelId' : channelId, 109 'channel' : channel, 110 'color' : color, 111 }; 112 113 }); 114 }; 115 116 /** 117 * Handle the removal of a channel 118 */ 119 $scope.removeChannel = function(channelId) { 120 121 // update UI 122 $scope.safeApply(function() { 123 124 // delete the channel 125 delete $scope.channelItems[channelId]; 126 }); 127 }; 128 129 /**

70


71

130 * Get a color for a new channel 195 * Get the channel items 131 */ 196 */ 132 $scope.getNewChannelColor = function() { 197 $scope.getChannelItems = function() { 133 198 return $scope.channelItems; 134 // variables 199 }; 135 var colors = $.extend([], $scope.COLORS); 200 136 var count = $scope.countChannelItems(); 201 /** 137 202 * Get the amount channel items 138 // loop all the current channels 203 */ 139 for (var channelId in $scope.channelItems) { 204 $scope.countChannelItems = function() { 140 205 return Object.keys($scope.channelItems).length; 141 // variables 206 }; 142 var channelItem = $scope.getChannelItem(channelId); 207 143 var color = channelItem.color; 208 /** 144 var index = colors.indexOf(color); 209 * Get a single channel item 145 210 */ 146 // check if the index is known 211 $scope.getChannelItem = function(channelId) { 147 if (index > ‐1) { 212 148 colors.splice(index, 1); 213 // guard: check if this channel item exists 149 } 214 if (!$scope.channelItemExists(channelId)) { 150 215 return undefined; 151 } 216 } 152 217 153 // guard: check if all the colors are gone 218 return $scope.channelItems[channelId]; 154 if (colors.length <= 0) { 219 }; 155 colors = $.extend([], $scope.COLORS); 220 156 } 221 /** 157 222 * Check whether a channel item exists 158 // return a random one 223 */ 159 return colors[Math.floor(Math.random() * colors.length)]; 224 $scope.channelItemExists = function(channelId) { 160 }; 225 return channelId in $scope.channelItems; 161 226 }; 162 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 227 163 /** GETTERS **/ 228 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 164 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 229 /** HELPERS 165 230 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 166 /** 231 167 * Get the DOM element of a channel 232 /** 168 */ 233 * A safe apply helper 169 $scope.getChannelElement = function(channelId) { 234 * SOURCE: http://stackoverflow.com/questions/22733422/angularjs‐rootscopeinprog‐inprogress‐error 170 return $('.channel[data="'+ channelId +'"]'); 235 */ 171 }; 236 $scope.safeApply = function (fn) { 172 237 var phase = $scope.$root.$$phase; 173 /** 238 if (phase == '$apply' || phase == '$digest') { 174 * Get the DOM element of the current selected channel 239 if (fn && typeof fn === 'function') { 175 */ 240 fn(); 176 $scope.getSelectedChannelElement = function() { 241 } 177 return $('.channel.selected'); 242 } else { 178 }; 243 $scope.$apply(fn); 179 244 } 180 /** 245 }; 181 * Get the controller for a specific channel 246 182 */ 247 /** 183 $scope.getChannelController = function(channelId) { 248 * Get the name of this controller 184 return getEngine().getChannelController(channelId); 249 */ 185 }; 250 $scope.getName = function () { 186 251 return 'channel‐list‐controller'; 187 /** 252 }; 188 * Get the DOM element of the current selected channel 253 189 */ 254 /** 190 $scope.getSelectedChannelController = function() { 255 * An object key helper 191 return angular.element($scope.getSelectedChannelElement().find('.channel__controller')).scope(); 256 * SOURCE: http://stackoverflow.com/questions/22691183/check‐object‐size‐in‐angularjs‐template‐doing‐ 192 }; 257 */ 193 258 $scope.keys = Object.keys; 194 /** 259

channellistcontroller.js


260 /** 261 * An object value helper 262 */ 263 $scope.values = Object.values; 264 265 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 266 /** ADMINISTRATIVE **/ 267 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 268 269 /** 270 * Destroy this controller instance. 271 */ 272 $scope.destruct = function() { 273 // empty 274 }; 275 276 // call initialize 277 $scope.initialize(); 278 }]);

channellistcontroller.js

72


1 /** 2 * The DataChannel class stores data for a specific probe 3 */ 4 var DataChannel = Class.create({ 5 6 /** 7 * Class ID 8 */ 9 id: 'DataChannel', 10 11 /** 12 * Initialize 13 */ 14 initialize: function (channelId, isDummy) { 15 16 // channel id 17 this.channelId = channelId; 18 19 // flag to identify whether this channel is a dummy 20 this.dummy = (isDummy === undefined ? false : isDummy); 21 22 // measurement type 23 this.measurementTypeId = ANALOG_MEASUREMENT_TYPE_ID; 24 this.sensorTypeId = UNKNOWN_SENSOR_TYPE_ID; 25 26 // array with all the data points 27 this.series = {}; 28 29 // mapping of the data points by x‐axis value and serieId 30 // to allow a quick lookup in the series array. 31 this.indexMap = {}; 32 this.xMap = {}; 33 this.indexOffsetMap = {}; 34 35 // flag to identify whether this channel is active 36 this.active = true; 37 38 // flag to identify whether the data set is modified 39 // and not rendered yet by the graphics 40 this.modified = true; 41 42 // counter to keep track of how many new points on the x‐axis are added 43 this.additionCount = 0; 44 }, 45 46 /** 47 * Add a new data point or a new set of points 48 * This array is according the following format: 49 * [serieId, x, y] 50 */ 51 addData : function(rawDataArr) { 52 53 // guard: check if the data channel is active 54 if (!this.active) { 55 return; 56 } 57 58 // variables 59 var serieId = ‐1; 60 var x = 0; 61 var y = 0; 62 63 // check for a packet from the serial 64 if (rawDataArr.length == 7) {

datachannel.js

65 66 // variables 67 var xByteArr = rawDataArr.slice(1, 5); 68 var yByteArr = rawDataArr.slice(5, rawDataArr.length); 69 70 // set parameters 71 serieId = rawDataArr[0]; 72 x = byteArrayToLong(xByteArr); 73 y = byteArrayToInt(yByteArr); 74 } 75 76 // check for a dummy packet generated by the app itself 77 else if (rawDataArr.length >= 3) { 78 79 // set parameters 80 serieId = rawDataArr[0]; 81 x = rawDataArr[1]; 82 y = rawDataArr[2]; 83 } 84 85 // guard: check if the data was valid 86 if (serieId < 0) { 87 error('An invalid data point is parsed to a data channel: '+ JSON.stringify(rawDataArr)); 88 return; 89 } 90 91 // guard: check if we are dealing with a negative y value 92 if (y > 60000) { 93 y = 65536 ‐ y; 94 } 95 96 // debug 97 /*debug('‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐'); 98 debug(rawDataArr.length); 99 debug(this.channelId); 100 debug(serieId); 101 debug(x); 102 debug(y);*/ 103 104 // check if the probe has been reset and we need to flush all the data 105 if (x < this.getLastX(serieId)) { 106 /*debug('‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐'); 107 debug(rawDataArr.length); 108 debug(this.channelId); 109 debug(serieId); 110 debug(x); 111 debug(y);*/ 112 this.clearData(); 113 } 114 115 // check if the serie exists 116 if (!this.serieExists(serieId)) { 117 this.addSerie(serieId); 118 } 119 120 // finally add the data 121 this.addDataPoint(serieId, x, y); 122 123 // increase the modification counter 124 this.additionCount ++; 125 126 // set this channel to modified 127 this.modify(); 128 }, 129

73


130 /** 131 * Check whether a data serie exists 132 */ 133 serieExists : function(serieId) { 134 return (serieId in this.series); 135 }, 136 137 /** 138 * Add a new serie 139 */ 140 addSerie : function(serieId) { 141 142 // debug 143 debug('Added a new serie to channel '+ this.channelId +' with ID: '+ serieId); 144 145 // add the serie 146 this.series[serieId] = []; 147 148 // add to mapping arrays 149 this.indexOffsetMap[serieId] = 0; 150 this.indexMap[serieId] = {}; 151 this.xMap[serieId] = {}; 152 }, 153 154 /** 155 * Add new data to a serie 156 */ 157 addDataPoint : function(serieId, x, y) { 158 159 // add the datapoint to the serie 160 var newLength = this.series[serieId].push(y); 161 162 // check if the length is too high 163 if (newLength > MAX_DATA_AMOUNT) { 164 165 // variables 166 var overflowAmount = newLength ‐ MAX_DATA_AMOUNT; 167 168 // increase the index offset to allow the mapping 169 // work properly still after the slice of the array 170 this.indexOffsetMap[serieId] += overflowAmount; 171 172 // slice the array with shift, this is way faster than slice 173 // SOURCE: https://jsperf.com/pop‐vs‐slice/5 174 //this.series[serieId] = this.series[serieId].slice(MAX_DATA_AMOUNT * ‐1); 175 while (overflowAmount‐‐ > 0) { 176 this.series[serieId].shift(); 177 } 178 179 // update the new length 180 newLength = MAX_DATA_AMOUNT; 181 } 182 183 // handle mapping for quick lookups 184 // and handling of the x axis 185 this.addMapping(serieId, x, y, newLength ‐ 1); 186 187 // set the modify flag 188 this.modify(); 189 }, 190 191 /** 192 * Add a data point to the mapping arrays 193 */ 194 addMapping : function (serieId, x, y, index) {

datachannel.js

195 196 // adjust the index to the offset 197 mapIndex = index + this.getIndexOffset(serieId); 198 199 // add to the mapping arrays 200 this.indexMap[serieId][x] = mapIndex; 201 this.xMap[serieId][mapIndex] = x; 202 }, 203 204 /** 205 * Get the series 206 */ 207 getSeries : function(domain, domainOffset) { 208 209 // variables 210 serieArr = []; 211 212 // loop all the series 213 for (var serieId in this.series) { 214 serieArr[serieId] = this.getSerie(serieId, domain, domainOffset); 215 } 216 217 return serieArr; 218 }, 219 220 /** 221 * Get the a single serie 222 */ 223 getSerie : function(serieId, domain, domainOffset) { 224 225 // guard: check if the requested domain is valid 226 if (domain === undefined) { 227 return this.series[serieId]; 228 } 229 230 // variables 231 var lastX = this.getLastX(serieId); 232 var requestedTime = Math.abs(domain[1]); // NOTE: ignore the domain[0] as we always want to render 233 var requestedIndex = this.getClosestIndex(serieId, lastX ‐ requestedTime, true, domainOffset); 234 var actualLength = this.getSerieSize(serieId); 235 var requestedLength = actualLength ‐ requestedIndex; 236 237 // debug 238 /*debug('Channel ID: '+ this.channelId); 239 debug('Last x: '+ lastX); 240 debug('Last datapoint: '+ this.getDataPointByX(serieId, lastX)); 241 debug('Requested length: '+ requestedLength);*/ 242 243 // slice off the last values 244 return this.series[serieId].slice(actualLength ‐ requestedLength, actualLength); 245 }, 246 247 /** 248 * Get the size of a single serie 249 */ 250 getSerieSize : function(serieId) { 251 return this.series[serieId].length; 252 }, 253 254 /** 255 * Get all the new data 256 */ 257 getNewData : function() { 258 259 // variables

74


260 var domain = [this.additionCount * ‐1, 0]; 261 262 // get all the data for this domain 263 return this.getSeries(domain); 264 }, 265 266 /** 267 * Get a data point by its x value 268 */ 269 getDataPointByX : function(serieId, x) { 270 271 // variables 272 var index = this.getIndex(serieId, x); 273 274 // fetch data point by index 275 return this.getDataPointByIndex(serieId, index); 276 }, 277 278 /** 279 * Get a data point by its index 280 */ 281 getDataPointByIndex : function(serieId, index) { 282 283 // variables 284 var serie = this.getSerie(serieId); 285 286 // guard: check if the index is valid 287 if (index < 0 || !(index in serie)) { 288 289 // return default data point 290 return DEFAULT_DATA_POINT; 291 } 292 293 return serie[index]; 294 }, 295 296 /** 297 * Get the newest data point available 298 */ 299 getLastDataPoint : function(serieId) { 300 301 // variables 302 var lastX = this.getLastX(serieId); 303 304 // fetch data point by x 305 return this.getDataPointByX(serieId, lastX); 306 }, 307 308 /** 309 * Get the previous data point in a serie with a certain x value 310 */ 311 getPreviousDataPointByX : function(serieId, x) { 312 313 // variables 314 var index = this.getIndex(serieId, x); 315 316 // get the data point 317 return this.getPreviousDataPointByIndex(serieId, index); 318 }, 319 320 /** 321 * Get the previous data point in a serie with a certain index 322 */ 323 getPreviousDataPointByIndex: function(serieId, index) { 324

datachannel.js

325 // variables 326 var previousIndex = index ‐ 1; 327 328 // get the data point 329 return this.getDataPointByIndex(serieId, previousIndex); 330 }, 331 332 /** 333 * Get the previous x in a serie by a certain x value 334 */ 335 getPreviousXByX : function(serieId, x) { 336 337 // variables 338 var index = this.getIndex(serieId, x); 339 340 // get the data point 341 return this.getPreviousXByIndex(serieId, index); 342 }, 343 344 /** 345 * Get the previous x by index 346 */ 347 getPreviousXByIndex: function(serieId, index) { 348 349 // variables 350 var previousIndex = index ‐ 1; 351 352 // get the data point 353 return this.getX(serieId, previousIndex); 354 }, 355 356 /** 357 * Get the x value for a specific index 358 */ 359 getX : function(serieId, index) { 360 361 // guard: check if the parameters are valid 362 if (serieId == undefined || index == undefined) { 363 error('The parameters for fetching an x are invalid. SerieId: '+ serieId + ', index: ' 364 return ‐1; 365 } 366 367 // variables 368 var mapIndex = index + this.getIndexOffset(serieId); 369 370 // guard: check if the index is valid 371 if (mapIndex < 0 || !(mapIndex in this.xMap[serieId])) { 372 373 // return invalid x 374 return ‐1; 375 } 376 377 return this.xMap[serieId][mapIndex]; 378 }, 379 380 /** 381 * Get the last x value 382 */ 383 getLastX : function(serieId) { 384 385 // guard: check if there is data 386 if (!(serieId in this.series) || this.series[serieId].length <= 0) { 387 388 // return invalid x 389 return ‐1;

75


390 } 391 392 // variables 393 var index = this.series[serieId].length ‐ 1; 394 395 return this.getX(serieId, index); 396 }, 397 398 /** 399 * Get the first x value 400 */ 401 getFirstX : function(serieId) { 402 403 // guard: check if there is data 404 if (!(serieId in this.series)) { 405 406 // return invalid x 407 return ‐1; 408 } 409 410 return this.getX(serieId, 0); 411 }, 412 413 /** 414 * Get the last x value 415 */ 416 getLastIndex : function(serieId) { 417 418 // guard: check if there is data 419 if (!(serieId in this.series) || this.series[serieId].length <= 0) { 420 421 // return invalid index 422 return 0; 423 } 424 425 // variables 426 return this.series[serieId].length ‐ 1; 427 }, 428 429 /** 430 * Get the data index of a x‐axis value 431 */ 432 getIndex : function(serieId, x) { 433 434 // guard: check if the parameters are valid 435 if (serieId == undefined || x == undefined) { 436 error('The parameters for fetching an index are invalid. SerieId: '+ serieId + ', x: ' 437 return ‐1; 438 } 439 440 // guard: check if the index is set 441 if (x < 0 || !(x in this.indexMap[serieId])) { 442 443 // return invalid index 444 return ‐1; 445 } 446 447 return this.indexMap[serieId][x] ‐ this.getIndexOffset(serieId); 448 }, 449 450 /** 451 * Get the closest index by an x value 452 */ 453 getClosestIndex : function(serieId, requestedX, overflow, offset) { 454

datachannel.js

455 // prepare parameters 456 overflow = (overflow === undefined ? false : overflow); 457 offset = (offset === undefined) ? 0 : parseInt(offset); 458 459 // variables 460 var tolerance = INDEX_TO_TIME_TOLERANCE; 461 var maxAttempts = overflow ? 1000 : INDEX_TO_TIME_MAX_ATTEMPTS; 462 var size = this.getSerieSize(serieId); 463 var attempts = 0; 464 var divider = overflow ? size / 50 : 2; 465 var direction = 1; 466 var closestIndex = overflow ? size ‐ 100 : parseInt(size / divider); 467 var closestX = this.getX(serieId, closestIndex); 468 469 // loop all the available indexes 470 while (Math.abs(closestX ‐ requestedX) > tolerance) { 471 472 // guard: check if the max attempts are overflown 473 if (attempts++ > maxAttempts) { 474 break; 475 } 476 477 // guard: check if we are allowed to overflow the x back in time 478 if (overflow && closestX < requestedX) { 479 break; 480 } 481 482 // variables 483 divider = divider * (overflow ? 1 : 2); 484 direction = requestedX > closestX && !overflow ? 1 : ‐1; 485 486 // get new indexes 487 closestIndex = closestIndex + parseInt(size / divider) * direction; 488 closestX = this.getX(serieId, closestIndex); 489 } 490 491 // add offset 492 closestIndex = closestIndex + (offset === undefined ? 0 : offset); 493 494 // guard: check if the x and index is valid 495 if (closestX < 0 || closestIndex < 0) { 496 return 0; 497 } 498 499 // debug 500 //debug('Get closest index result. Requested: '+ requestedX +'. Closest: '+ this.getX(serieId, clo 501 502 // return index 503 return closestIndex; 504 }, 505 506 /** 507 * Clear all the data in this channel 508 */ 509 clearData : function() { 510 511 // debug 512 debug('A data channel has been cleared. Channel ID: '+ this.channelId); 513 514 // reset the parameters 515 this.series = []; 516 this.indexMap = {}; 517 this.xMap = {}; 518 this.indexOffsetMap = {}; 519 this.active = true;

76


520 this.modified = false; 521 this.additionCount = 0; 522 }, 523 524 /** 525 * Get the last update time 526 */ 527 getLastUpdateTime : function(serieId) { 528 return this.getLastX(serieId); 529 }, 530 531 /** 532 * Calculate the frequency of data retreival 533 * based on several latest data points 534 */ 535 calculateFrequency : function() { 536 537 // TODO: implement this 538 }, 539 540 /** 541 * Set the measurment type 542 */ 543 setMeasurementType : function(typeId) { 544 545 // debug 546 debug('The measurement type of channel '+ this.channelId +' is changed to: '+ typeId); 547 548 // set to new type 549 this.measurementTypeId = typeId; 550 }, 551 552 /** 553 * Check whether this channel is an analog signal 554 */ 555 isAnalogMeasurement : function() { 556 return this.measurementTypeId == ANALOG_MEASUREMENT_TYPE_ID; 557 }, 558 559 /** 560 * Check whether this channel is a digital signal 561 */ 562 isDigitalMeasurement : function() { 563 return this.measurementTypeId == DIGITAL_MEASUREMENT_TYPE_ID; 564 }, 565 566 /** 567 * Check whether this channel is a specific sensor type 568 */ 569 isSensor : function(sensorTypeId) { 570 return this.sensorTypeId == sensorTypeId; 571 }, 572 573 /** 574 * Check whether this channel is a dummy 575 */ 576 isDummy : function() { 577 return this.dummy; 578 }, 579 580 /** 581 * Check whether this channel is modified 582 */ 583 isModified : function() { 584 return this.modified;

datachannel.js

585 }, 586 587 /** 588 * Set the modify flag to true 589 */ 590 modify : function() { 591 this.modified = true; 592 }, 593 594 /** 595 * Set the modify flag to false 596 */ 597 unmodify : function() { 598 599 // reset counters 600 this.additionCount = 0; 601 602 // reset flag 603 this.modified = false; 604 }, 605 606 /** 607 * Check whether this channel is active 608 */ 609 isActive : function() { 610 return this.active; 611 }, 612 613 /** 614 * Set to inactive 615 */ 616 disable : function() { 617 this.active = false; 618 }, 619 620 /** 621 * Set to active 622 */ 623 enable : function() { 624 this.active = true; 625 }, 626 627 /** 628 * Get the channel id 629 */ 630 getChannelId : function() { 631 return this.channelId; 632 }, 633 634 /** 635 * Get the of the index for all the mapping arrays 636 */ 637 getIndexOffset : function(serieId) { 638 639 // guard: check if the serie id is known 640 if (!(serieId in this.indexOffsetMap)) { 641 return 0; 642 } 643 644 return this.indexOffsetMap[serieId]; 645 } 646 });

77


1 /** 2 * The Graphic class used to handle visualization. 3 */ 4 var Graphic = Class.create({ 5 6 /** 7 * Class ID 8 */ 9 id: 'Graphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function (dataChannel, graphicRenderer, options) { 15 16 // storage of the latest rendered data 17 this.originalDataPointArr = {}; 18 this.dataPointArr = {}; 19 20 // states 21 this.paused = false; 22 this.pausedTime = 0; 23 24 // y domain auto scaler 25 this.lowestDataPoint = 0; 26 this.highestDataPoint = 0; 27 28 // manipulations 29 this.manipulations = []; 30 this.transitionRequested = false; 31 this.transitionDuration = 0; 32 this.transitionExecuting = false; 33 34 // the graphic options 35 this.graphicId = null; 36 this.graphicRenderer = null; 37 this.graphicType = null; 38 this.interpolationType = null; 39 40 // storage for all the data 41 this.dataChannel = null; 42 this.dataChannelId = null; 43 44 // container size 45 this.container = null; 46 this.dimensions = []; 47 48 // scales describing the data domains 49 this.xDomain = []; 50 this.yDomain = []; 51 52 // data mapping to screen 53 this.xRange = []; 54 this.yRange = []; 55 56 // scales describing the data domains 57 this.xScale = null; 58 this.yScale = null; 59 60 // axis showing mapping of values 61 this.axisEnabled = getFromArray('axisEnabled', options, false); 62 this.voltageTicksEnabled = true; 63 this.xAxis = null; 64 this.yAxis = null;

graphic.js

65 66 // prepare variables 67 var thisObject = this; 68 69 // initialize the graphic 70 this.graphicId = getFromArray('graphicId', options, ‐1); 71 this.graphicRenderer = graphicRenderer; 72 73 // initialize the data channel 74 this.dataChannel = dataChannel; 75 this.dataChannelId = dataChannel.getChannelId(); 76 77 // set the container and dimensions 78 this.container = getEngine().getGraphicManager().getGraphicContainer(this, this.dataChannelId); 79 this.updateDimensions(); 80 // x scale instance 81 this.xScale = d3.scale.linear(); 82 83 // y scale instance 84 this.yScale = d3.scale.linear(); 85 86 87 // initialize the x domain 88 this.setXDomain(0, 11000); 89 90 // initialize the y domain 91 // NOTE: invert the y‐axis 92 this.setYDomain(MAX_DATA_VALUE * 1.1, MIN_DATA_VALUE); 93 94 // initialize the x range 95 this.setXRange(0, this.getWidth()); 96 97 // initialize the y range 98 this.setYRange(0, this.getHeight()); 99 100 // initialize the graphic 101 this.setGraphicType(getFromArray('graphicType', options, LINE_GRAPHIC_TYPE)); 102 103 // set the interpolation 104 this.setInterpolationType(getFromArray('interpolationType', options, LINEAR_INTERPOLATION_TYPE)); 105 106 // initialize manipulations 107 this.setTransitionDuration(getFromArray('transitionDuration', options, DEFAULT_TRANSITION_DURATION) 108 109 // update the axis 110 this.updateXAxis(); 111 this.updateYAxis(); 112 113 // initialize the axis 114 this.initializeXAxis(); 115 this.initializeYAxis(); 116 117 // add possible classes to the drawing element 118 if (this.axisEnabled) { 119 $(this.getElement()).addClass('axis‐enabled'); 120 } 121 }, 122 123 /** 124 * Calculate the x coordinate of a certain data point 125 */ 126 getXCoordinate : function(serieId, dataPoint, graphicIndex) { 127 128 // guard: check if the serie id is valid 129 if (serieId === undefined) {

78


130 return 0; 131 } 132 133 // calculator variables 134 var dataChannelIndex = this.calculateDataChannelIndex(serieId, graphicIndex); 135 var minX = this.dataChannel.getLastX(serieId) ‐ this.xDomain[0]; 136 var currentX = this.dataChannel.getX(serieId, dataChannelIndex); 137 138 // guard: check for a start and end identifier 139 // which will make sure we can make a closed shape 140 if (dataPoint === 'start' || dataChannelIndex < 0) { 141 currentX = this.dataChannel.getFirstX(serieId); 142 } else if (dataPoint === 'end') { 143 currentX = this.dataChannel.getLastX(serieId); 144 } 145 146 // return the X coordinate where we want to plot this datapoint 147 return this.xScale((currentX ‐ minX) * ‐1 + this.xDomain[0]); 148 }, 149 150 /** 151 * Calculate the y coordinate of a certain data point 152 */ 153 getYCoordinate : function(serieId, dataPoint, graphicIndex, coordinateType) { 154 155 // variables 156 var originalDataPoint = dataPoint; 157 158 // guard: check if the serie id is valid 159 if (serieId === undefined) { 160 return 0; 161 } 162 163 // guard: check for a start and end identifier 164 // which will make sure we can make a closed shape 165 if (dataPoint === 'start' || dataPoint === 'end') { 166 //dataPoint = this.yDomain[1]; 167 dataPoint = this.yDomain[1] * 2; 168 } 169 170 // check for a new loop 171 if (graphicIndex === 0) { 172 173 // check for clipping 174 if (this.highestDataPoint > ABSOLUTE_MAX_DATA_VALUE) { 175 this.highestDataPoint = ABSOLUTE_MAX_DATA_VALUE; 176 } 177 if (this.lowestDataPoint < ABSOLUTE_MIN_DATA_VALUE) { 178 this.lowestDataPoint = ABSOLUTE_MIN_DATA_VALUE; 179 } 180 181 // debug 182 //debug('Y‐axis update with lowest: '+ this.lowestDataPoint + ' and highest: '+ this.highestDataPoint); 183 184 // set the y domain 185 this.setYDomain(this.highestDataPoint, this.lowestDataPoint); 186 this.updateYAxis(100); 187 188 // reset the y domain 189 this.lowestDataPoint = MIN_DATA_VALUE; 190 this.highestDataPoint = MAX_DATA_VALUE * 1.1; 191 } 192 193 // calculator variables 194 var dataChannelIndex = this.calculateDataChannelIndex(serieId, graphicIndex);

graphic.js

195 var x = this.dataChannel.getX(serieId, dataChannelIndex); 196 var newDataPoint = dataPoint; 197 198 // loop all the maniuplations to apply data changes 199 for (var index in this.manipulations) { 200 201 // variables 202 var manipulation = this.manipulations[index]; 203 204 // guard: check if the datapoint is valid 205 if (originalDataPoint === 'start' || originalDataPoint === 'end') { 206 break; 207 } 208 209 // check if we need to prepare the manipulation as we are in a new data cycle 210 if (graphicIndex === 0) { 211 manipulation.__prepare(); 212 } 213 214 // apply the manipulation 215 newDataPoint = manipulation.__calculate( 216 serieId, 217 x, 218 newDataPoint, 219 graphicIndex, 220 dataChannelIndex 221 ); 222 223 // guard: check for clipping 224 if (newDataPoint > ABSOLUTE_MAX_DATA_VALUE) { 225 newDataPoint = ABSOLUTE_MAX_DATA_VALUE; 226 } else if (newDataPoint < ABSOLUTE_MIN_DATA_VALUE) { 227 newDataPoint = ABSOLUTE_MIN_DATA_VALUE; 228 } 229 230 // update the data point in the array 231 // NOTE: this is to have in between statuses for other manipulations 232 this.dataPointArr[serieId][graphicIndex] = newDataPoint; 233 } 234 235 // check for a new lowest 236 if (newDataPoint < this.lowestDataPoint && newDataPoint > ABSOLUTE_MIN_DATA_VALUE) { 237 this.lowestDataPoint = newDataPoint; 238 } else if (newDataPoint > this.highestDataPoint && newDataPoint < ABSOLUTE_MAX_DATA_VALUE) { 239 this.highestDataPoint = newDataPoint; 240 } 241 242 // calculate the y coordinate 243 var yCoord = this.yScale(newDataPoint); 244 245 // guard: check if the y coord is valid 246 if (isNaN(yCoord)) { 247 return 0; 248 } 249 250 // return the Y coordinate where we want to plot this datapoint 251 return yCoord; 252 }, 253 254 /** 255 * Add a new manipulation 256 */ 257 addManipulation : function(manipulation) { 258 259 // guard: check if the manipulation is valid

79


260 if (_.isEmpty(manipulation)) { 261 return; 262 } 263 264 // debug 265 debug('A new manipulation has been added to a channel. Channel: '+ this.dataChannel.channelId + 266 267 // add to the array 268 this.manipulations.push(manipulation); 269 }, 270 271 /** 272 * Add a new manipulation 273 */ 274 addManipulationById : function(manipulationId) { 275 276 // variables 277 var manipulation = null; 278 279 // switch on the type 280 switch (manipulationId) { 281 282 // moving average 283 case MOVING_AVERAGE_MANIPULATION_TYPE: 284 manipulation = new MovingAverageManipulation(this); 285 break; 286 287 // moving average 288 case DIFFERENTIAL_MANIPULATION_TYPE: 289 manipulation = new DifferentialManipulation(this); 290 break; 291 292 // moving average 293 case INVERSE_MANIPULATION_TYPE: 294 manipulation = new InverseManipulation(this); 295 break; 296 297 // moving average 298 case THRESHOLD_MANIPULATION_TYPE: 299 manipulation = new ThresholdManipulation(this); 300 break; 301 302 // moving average 303 case FFT_MANIPULATION_TYPE: 304 manipulation = new FFTManipulation(this); 305 break; 306 } 307 308 // add the manipulation 309 this.addManipulation(manipulation); 310 }, 311 312 /** 313 * Get a single manipulation by type 314 */ 315 getManipulationById : function(manipulationId) { 316 317 // variables 318 var manipulations = this.getManipulations(manipulationId); 319 320 // guard: check if any are found 321 if (_.isEmpty(manipulations)) { 322 return false; 323 } 324

graphic.js

325 return manipulations.shift(); 326 }, 327 328 /** 329 * Get a set of manipulations by type 330 */ 331 getManipulationsById : function(manipulationId) { 332 333 // variables 334 var manipulations = []; 335 336 // loop all the manipulations to search this specific type 337 for (var manipulationIndex in this.manipulations) { 338 339 // variables 340 manipulation = this.manipulations[manipulationIndex]; 341 342 // guard: check if the id matched 343 if (manipulation.id != manipulationId) { 344 continue; 345 } 346 347 manipulations.push(manipulation); 348 } 349 350 return manipulations; 351 }, 352 353 /** 354 * Call this method when there has been a significant change and the whole 355 * chart is required to be re‐rendered. E.g. when switching the differentiate toggle 356 */ 357 redraw : function(requestTransition) { 358 359 // debug 360 debug('Graphic '+ this.dataChannel.channelId +' is going to be redrawn with transitions.' 361 362 // request transition 363 if (requestTransition !== false) { 364 this.requestTransition(); 365 } 366 367 // check if we need to render instantly 368 // this is needed when the graphics are paused 369 if (!getEngine().getGraphicManager().isRunning() || !this.isRunning()) { 370 this.render(true); 371 } 372 }, 373 374 /** 375 * Render the graph with new data points 376 */ 377 render : function(updateOnly) { 378 379 // guard: check if the data channel was modified 380 if (!this.dataChannel.isModified() && !updateOnly) { 381 return; 382 } 383 384 // initialize the render 385 this.initializeRender(); 386 387 // variables 388 var domainOffset = this.calculateDomainOffset(); 389

80


390 // check if we need to fetch the new data 391 if (!updateOnly) { 392 393 // get from the data channel 394 this.originalDataPointArr = this.dataChannel.getSeries(this.xDomain, domainOffset); 395 } 396 397 // fetch the new series 398 this.dataPointArr = jQuery.extend(true, {}, this.originalDataPointArr); 399 400 // render the indicator for the latest data point 401 this.renderLatestDataIndicators(); 402 403 // render all the series 404 this.renderSeries(); 405 406 // reset the animation request 407 this.resetTransition(); 408 409 // disable modify flag 410 //this.dataChannel.unmodify(); 411 }, 412 413 /** 414 * Method called before rendering 415 */ 416 initializeRender : function() { 417 // implementable 418 }, 419 420 /** 421 * Render all the series 422 */ 423 renderSeries : function() { 424 425 // loop all the series 426 for (var serieId in this.dataPointArr) { 427 428 // render this serie 429 this.renderSerie(serieId); 430 } 431 }, 432 433 /** 434 * Render all the latest data point indicator 435 */ 436 renderLatestDataIndicators : function() { 437 438 // loop all the series 439 for (var serieId in this.dataPointArr) { 440 441 // render this indicator 442 this.renderLatestDataIndicator(serieId); 443 } 444 }, 445 446 /** 447 * Render the serie 448 */ 449 renderSerie : function(serieId) { 450 // implementable by child 451 }, 452 453 /** 454 * Render the latest data point indicator

graphic.js

455 */ 456 renderLatestDataIndicator : function(serieId) { 457 // implementable by child 458 }, 459 460 /** 461 * Calculate an additional offset that might be required for manipulation calculations 462 * when fetching the handled data points from the data channel set. 463 * This is for example needed for the moving average manipulation where a certain set of previous val 464 */ 465 calculateDomainOffset : function() { 466 467 // variables 468 var offset = 0; 469 470 // loop all manipulations 471 // NOTE: don't check if the manipulation is active! 472 // this will screw up the data set when we are switching between active and inactive 473 for (var manipulationIndex in this.manipulations) { 474 475 // variables 476 var manipulation = this.manipulations[manipulationIndex]; 477 478 // guard: check if this manipulation is a moving average 479 if (manipulation.id !== 'MovingAverageManipulation') { 480 continue; 481 } 482 483 // check if the maximum data points required is higher than the offset 484 if (manipulation.dataPointAmount > Math.abs(offset)) { 485 offset = manipulation.dataPointAmount * ‐1; 486 } 487 } 488 489 return offset; 490 }, 491 492 /** 493 * Check whether this graph is running 494 */ 495 isRunning : function() { 496 return !this.paused; 497 }, 498 499 /** 500 * Start running 501 */ 502 unpause : function() { 503 this.paused = false; 504 this.pausedTime = 0; 505 }, 506 507 /** 508 * Start running 509 */ 510 pause : function() { 511 this.paused = true; 512 this.pausedTime = Date.now(); 513 }, 514 515 /** 516 * Get the drawing element for this graphic 517 */ 518 getElement : function() { 519 return this.getSVG() !== undefined ? this.getSVG() : this.getCanvas();

81


520 }, 521 522 /** 523 * Get the data channel for this graphic 524 */ 525 getDataChannel : function() { 526 return this.dataChannel; 527 }, 528 529 /** 530 * Get the dimensions of the graph on screen in pixels 531 */ 532 getDimensions : function() { 533 return this.dimensions; 534 }, 535 536 /** 537 * Update the dimensions according to the container and state of the graphic 538 */ 539 updateDimensions : function() { 540 541 // variables 542 var width = $(this.container).width(); 543 var height = $(this.container).height(); 544 545 // add multipliers depending on the state 546 if (this.axisEnabled) { 547 width *= 0.9; 548 height *= 0.9; 549 } else { 550 551 } 552 553 // set the dimensions 554 this.setDimensions(width, height); 555 }, 556 557 /** 558 * Set the dimensions of the graph on screen in pixels 559 */ 560 setDimensions : function(width, height) { 561 this.dimensions = [width, height]; 562 }, 563 564 /** 565 * Get the width of the graph on screen in pixels 566 */ 567 getWidth : function() { 568 return this.dimensions[0]; 569 }, 570 571 /** 572 * Set the width of the graph on screen in pixels 573 */ 574 setWidth : function(width) { 575 this.dimensions[0] = width; 576 }, 577 578 /** 579 * Get the height of the graph on screen in pixels 580 */ 581 getHeight : function() { 582 return this.dimensions[1]; 583 }, 584

graphic.js

585 /** 586 * Set the height of the graph on screen in pixels 587 */ 588 setHeight : function(height) { 589 this.dimensions[1] = height; 590 }, 591 592 /** 593 * Set the domain for the x‐axis 594 */ 595 setXDomain : function(start, end) { 596 597 // update the domain 598 this.xDomain = [start, end]; 599 600 // reinitialize the graphic 601 this.xScale.domain(this.xDomain); 602 }, 603 604 /** 605 * Set the domain for the y‐axis 606 */ 607 setYDomain : function(start, end) { 608 609 // update the domain 610 this.yDomain = [start, end]; 611 612 // reinitialize the grpahic 613 this.yScale.domain(this.yDomain); 614 }, 615 616 /** 617 * Set the range for the x‐axis 618 */ 619 setXRange : function(start, end) { 620 621 // update the domain 622 this.xRange = [start, end]; 623 624 // reinitialize the grpahic 625 this.xScale.range(this.xRange); 626 }, 627 628 /** 629 * Set the range for the y‐axis 630 */ 631 setYRange : function(start, end) { 632 633 // update the domain 634 this.yRange = [start, end]; 635 636 // reinitialize the grpahic 637 this.yScale.range(this.yRange); 638 }, 639 640 /** 641 * Set the axis for the x‐axis 642 */ 643 updateXAxis : function(transitionDuration) { 644 645 // variables 646 var thisObject = this; 647 648 // guard: check for valid svg 649 if (_.isEmpty(this.svg)) {

82


650 return; 651 } 652 653 // update the axis 654 this.xAxis = d3.svg.axis() 655 .scale(this.xScale) 656 .orient('bottom') 657 .ticks(10) 658 .tickFormat(function(d) { 659 660 // guard: check for the last one 661 if (d == thisObject.xDomain[0]) { 662 //return ''; 663 } 664 665 // guard: check for now 666 if (d === 0) { 667 return 'now'; 668 } 669 670 // default 671 return parseInt(d / 1000) + 's'; 672 }); 673 674 // update the x‐axis 675 this.svg.selectAll('.x‐axis') 676 .transition() 677 .duration(transitionDuration === undefined ? this.transitionDuration : transitionDuration) 678 .call(this.xAxis); 679 }, 680 681 /** 682 * Set the axis for the y‐axis 683 */ 684 updateYAxis : function(transitionDuration) { 685 686 // variables 687 var thisObject = this; 688 689 // guard: check for valid svg 690 if (_.isEmpty(this.svg)) { 691 return; 692 } 693 694 // update the domain 695 this.yAxis = d3.svg.axis() 696 .scale(this.yScale) 697 .orient('left') 698 .ticks(10) 699 .tickFormat(function(d) { 700 701 // guard: check for no voltage ticks 702 if (!thisObject.voltageTicksEnabled) { 703 return d; 704 } 705 706 // format 707 return thisObject.formatToVoltage(d); 708 }); 709 710 // update the x‐axis 711 this.svg.selectAll('.y‐axis') 712 .transition() 713 .duration(transitionDuration === undefined ? this.transitionDuration : transitionDuration) 714 .call(this.yAxis);

graphic.js

715 }, 716 717 /** 718 * Format a data point to a voltage 719 */ 720 formatToVoltage : function(d) { 721 722 // variables 723 var formatter = d3.format(',.1f'); 724 var voltage = parseFloat(d) / MAX_DATA_VALUE * MAX_DATA_VALUE_VOLTAGE; 725 726 return formatter(voltage) + 'V'; 727 }, 728 729 /** 730 * Toggle type of formatting of ticks on the y‐axis (voltage or absolute) 731 */ 732 toggleYAxisTicks : function() { 733 this.voltageTicksEnabled = !this.voltageTicksEnabled; 734 this.updateYAxis(); 735 }, 736 737 /** 738 * Initialize the x‐axis 739 */ 740 initializeXAxis : function() { 741 // implementable 742 }, 743 744 /** 745 * Initialize the y‐axis 746 */ 747 initializeYAxis : function() { 748 // implementable 749 }, 750 751 /** 752 * Set the range for the y‐axis 753 */ 754 setInterpolationType : function(type) { 755 756 // set local variable 757 this.interpolationType = type; 758 }, 759 760 /** 761 * Set the duration of the transition animation 762 */ 763 setTransitionDuration : function(duration) { 764 765 // set local variable 766 this.transitionDuration = duration; 767 }, 768 769 /** 770 * Request a transition animation for the next frame 771 */ 772 requestTransition : function() { 773 774 // set local variable 775 this.transitionRequested = true; 776 }, 777 778 /** 779 * Reset the transition flag

83


780 */ 781 resetTransition : function() { 782 783 // guard: check if the flag is already disabled 784 if (!this.transitionRequested) { 785 return; 786 } 787 788 // variables 789 var thisObject = this; 790 791 // set transition is executing flag 792 this.transitionExecuting = true; 793 794 // set timeout 795 setTimeout(function() { 796 797 // reset the flag 798 thisObject.transitionExecuting = false; 799 }, 20); 800 801 // reset the flag 802 this.transitionRequested = false; 803 }, 804 805 /** 806 * Calculate the index of a datapoint in the data channel object 807 * as this graphic object only contains the sliced index 808 */ 809 calculateDataChannelIndex : function(serieId, graphicIndex) { 810 811 // variables 812 var dataChannelSerieSize = this.dataChannel.getSerieSize(serieId); 813 var graphicSerieSize = this.dataPointArr[serieId].length; 814 var dataChannelIndex = dataChannelSerieSize ‐ graphicSerieSize + graphicIndex; 815 816 return dataChannelIndex; 817 }, 818 819 /** 820 * Get the previous data point in a serie with a certain index 821 */ 822 getPreviousDataPointByIndex: function(serieId, index) { 823 824 // variables 825 var previousIndex = index ‐ 1; 826 827 // get the data point 828 return this.getDataPointByIndex(serieId, previousIndex); 829 }, 830 831 /** 832 * Get a data point by its index 833 */ 834 getDataPointByIndex : function(serieId, index) { 835 836 // guard: check if the index is valid 837 if (index < 0 || !(index in this.dataPointArr[serieId])) { 838 839 // return default data point 840 return DEFAULT_DATA_POINT; 841 } 842 843 return this.dataPointArr[serieId][index]; 844 },

graphic.js

845 846 /** 847 * Set the range for the y‐axis 848 */ 849 setGraphicType : function(graphicType) { 850 851 // guard: check if the type has changed 852 if (this.graphicType == graphicType) { 853 return; 854 } 855 856 // guard: check if this is the initial graphic type 857 // so we won't need to change the graphic fully 858 if (_.isEmpty(this.graphicType)) { 859 this.graphicType = graphicType; 860 return; 861 } 862 863 // remove this graphic 864 this.destruct(); 865 866 // create a new graphic instance with the same data channel 867 // NOTE: this will overwrite the existing graphic as only one graphic per 868 // data channel is allowed at the moment 869 getEngine().getGraphicManager().addGraphic( 870 this.dataChannel, 871 this.graphicRenderer, 872 graphicType 873 ); 874 }, 875 876 /** 877 * Get the color of this channel as HEX 878 */ 879 getHexColor : function() { 880 return $(this.container).attr('color‐data'); 881 }, 882 883 /** 884 * Remove this graphic 885 */ 886 destruct : function() { 887 888 // variables 889 var element = null; 890 891 // switch on the graphic renderer 892 switch (this.graphicRenderer) { 893 894 // ThreeJS 895 case THREE_GRAPHIC_RENDERER: 896 element = this.getCanvas(); 897 break; 898 899 // D3 900 case D3_GRAPHIC_RENDERER: 901 element = this.getSVG(); 902 break; 903 } 904 905 // remove the element from the DOM 906 $(element).remove(); 907 908 // finally remove the instance from the manager 909 getEngine().getGraphicManager().removeGraphic(this.graphicId);

84


1 /** 2 * A graphic using D3 as renderer 3 */ 4 var D3Graphic = Class.create(Graphic, { 5 6 /** 7 * Class ID 8 */ 9 id: 'D3Graphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function ($super, dataChannel, options) { 15 16 // variables 17 var dataChannelId = dataChannel.getChannelId(); 18 var container = getEngine().getGraphicManager().getGraphicContainer(this, dataChannelId, 19 var width = $(container).width(); 20 var height = $(container).height(); 21 22 // initialize the svg 23 this.svg = d3.select(container) 24 .append('svg') 25 .attr('data', dataChannel.getChannelId()) 26 .attr('width', width) 27 .attr('height', height); 28 29 // initialize parent 30 $super(dataChannel, D3_GRAPHIC_RENDERER, options); 31 }, 32 33 /** 34 * Get the SVG element 35 */ 36 getSVG : function() { 37 return $(this.container).find('svg[data="'+ this.dataChannel.getChannelId() +'"]'); 38 }, 39 });

d3graphic.js

85


1 /** 2 * A Graphic instance using ThreeJS 3 */ 4 var ThreeGraphic = Class.create(Graphic, { 5 6 /** 7 * Class ID 8 */ 9 id: 'ThreeGraphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function ($super, dataChannel, options) { 15 16 // variables 17 var dataChannelId = dataChannel.getChannelId(); 18 var screenWidth = getScreenWidth(); 19 var screenHeight = getScreenHeight(); 20 var WIDTH = screenWidth; 21 var HEIGHT = screenHeight; 22 23 // set some camera attributes 24 var VIEW_ANGLE = 45, 25 ASPECT = WIDTH / HEIGHT, 26 NEAR = 0.1, 27 FAR = 10000; 28 29 // prepare variables 30 this.isDark = $('body').is('.dark') || getFromArray('isDark', options, false); 31 this.mouseX = undefined; 32 this.mouseY = undefined; 33 34 // create a WebGL renderer, camera 35 // and a scene 36 this.renderer = new THREE.WebGLRenderer({ alpha: !this.isDark }); 37 this.scene = new THREE.Scene(); 38 this.camera = 39 new THREE.PerspectiveCamera( 40 VIEW_ANGLE, 41 ASPECT, 42 NEAR, 43 FAR); 44 45 // create a camera matrix to check if objects are visible to the viewport 46 this.frustum = new THREE.Frustum(); 47 this.cameraViewProjectionMatrix = new THREE.Matrix4(); 48 49 // create a point light 50 this.mainLight = new THREE.PointLight(0xFFFFFF); 51 52 // set its position 53 this.mainLight.position.x = 10; 54 this.mainLight.position.y = 50; 55 this.mainLight.position.z = 130; 56 57 // add the camera to the scene 58 this.scene.add(this.camera); 59 60 // the camera starts at 0,0,0 61 // so pull it back 62 this.camera.position.z = 300; 63 64 // start the renderer

threegraphic.js

65 this.renderer.setSize(WIDTH, HEIGHT); 66 this.renderer.setPixelRatio(window.devicePixelRatio); 67 68 // create a new canvas element 69 $canvas = $(this.renderer.domElement); 70 $canvas.attr('data', dataChannel.getChannelId()); 71 72 // add to the right graphic container 73 getEngine().getGraphicManager().getGraphicContainer(this, dataChannelId).append($canvas); 74 75 // add to the scene 76 this.scene.add(this.mainLight); 77 78 // initialize events 79 document.addEventListener('mousemove', this.onDocumentMouseMove, false); 80 document.addEventListener('touchstart', this.onDocumentTouchStart, false); 81 document.addEventListener('touchmove', this.onDocumentTouchMove, false); 82 window.addEventListener('resize', this.onWindowResize, false); 83 84 // initialize parent 85 $super(dataChannel, THREE_GRAPHIC_RENDERER, options); 86 }, 87 88 /** 89 * Method called before rendering 90 */ 91 initializeRender : function() { 92 // every time the camera or objects change position (or every frame) 93 this.camera.updateMatrixWorld(); // make sure the camera matrix is updated 94 this.camera.matrixWorldInverse.getInverse(this.camera.matrixWorld); 95 this.cameraViewProjectionMatrix.multiplyMatrices(this.camera.projectionMatrix, 96 this.frustum.setFromMatrix(this.cameraViewProjectionMatrix); 97 98 }, 99 100 /** 101 * Check whether an object is visible to the camera 102 */ 103 isVisible : function(object) { 104 return this.frustum.intersectsObject(object); 105 }, 106 107 /** 108 * Handle window resizes 109 */ 110 onWindowResize : function() { 111 112 // set camera 113 this.camera.aspect = (getScreenWidth() / 2) / (getScreenHeight() / 2); 114 this.camera.updateProjectionMatrix(); 115 116 // resize 117 this.renderer.setSize(getScreenWidth(), getScreenHeight()); 118 }, 119 120 /** 121 * Mouse move vent 122 */ 123 onDocumentMouseMove : function(event) { 124 this.mouseX = event.clientX ‐ getScreenWidth() / 2; 125 this.mouseY = event.clientY ‐ getScreenHeight() / 2; 126 }, 127 128 /** 129 * Touch start event

86


130 */ 131 onDocumentTouchStart : function(event) { 132 133 // check if there is a touch event 134 if (event.touches.length === 1) { 135 event.preventDefault(); 136 this.mouseX = event.touches[0].pageX ‐ getScreenWidth() / 2; 137 this.mouseY = event.touches[ 0 ].pageY ‐ getScreenHeight() / 2; 138 } 139 }, 140 141 /** 142 * Touch move event 143 */ 144 onDocumentTouchMove : function(event) { 145 146 // check if there is a touch event 147 if (event.touches.length === 1) { 148 event.preventDefault(); 149 this.mouseX = event.touches[0].pageX ‐ windowHalfX; 150 this.mouseY = event.touches[0].pageY ‐ windowHalfY; 151 } 152 }, 153 154 /** 155 * Get the renderer instance 156 */ 157 getRenderer : function() { 158 return this.renderer; 159 }, 160 161 /** 162 * Get the scene instance 163 */ 164 getScene : function() { 165 return this.scene; 166 }, 167 168 /** 169 * Get the camera instance 170 */ 171 getCamera : function() { 172 return this.camera; 173 }, 174 175 /** 176 * Get the main light instance 177 */ 178 getMainLight : function() { 179 return this.mainLight; 180 }, 181 182 /** 183 * Get the canvas element for this graphic 184 */ 185 getCanvas : function() { 186 return $(this.container).find('canvas[data="'+ this.dataChannel.getChannelId() +'"]'); 187 }, 188 });

threegraphic.js

87


1 /** 2 * A standard 2D line graphic instance 3 */ 4 var LineGraphic = Class.create(D3Graphic, { 5 6 /** 7 * Class ID 8 */ 9 id: 'LineGraphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function ($super, dataChannel, options) { 15 16 // variables 17 var thisObject = this; 18 19 // initialize local variables 20 this.strokeEnabled = getFromArray('strokeEnabled', options, true); 21 this.fillEnabled = getFromArray('fillEnabled', options, false); 22 23 // create a line object that represents the SVN line we're creating 24 this.lineGraphic = d3.svg.line(); 25 this.shapeGraphic = d3.svg.line(); 26 27 // initialize parent 28 $super(dataChannel, options); 29 30 // assign the x axis function 31 this.lineGraphic.x(function(dataPoint, graphicIndex) { 32 33 // variables 34 var serieId = $(this).attr('data'); 35 36 // calculate the x coordinate 37 return thisObject.getXCoordinate(serieId, dataPoint, graphicIndex); 38 }); 39 40 // assign the x axis function 41 this.shapeGraphic.x(function(dataPoint, graphicIndex) { 42 43 // variables 44 var serieId = $(this).attr('data'); 45 46 // calculate the x coordinate 47 return thisObject.getXCoordinate(serieId, dataPoint, graphicIndex ‐ 1); 48 }); 49 50 // assign the y axis function 51 this.lineGraphic.y(function(dataPoint, graphicIndex) { 52 53 // variables 54 var serieId = $(this).attr('data'); 55 56 // calculate the x coordinate 57 return thisObject.getYCoordinate(serieId, dataPoint, graphicIndex); 58 }); 59 60 // assin the y axis function 61 this.shapeGraphic.y(function(dataPoint, graphicIndex) { 62 63 // variables 64 var serieId = $(this).attr('data');

linegraphic.js

65 66 // calculate the x coordinate 67 return thisObject.getYCoordinate(serieId, dataPoint, graphicIndex ‐ 1); 68 }); 69 }, 70 71 /** 72 * Render a single serie its new data points 73 */ 74 renderSerie : function(serieId) { 75 76 // debug 77 //debug('The new graphic data point array: '+ this.dataPointArr[serieId].length); 78 /*console.log(this.svg.attr('data')); 79 console.log(serieId); 80 console.log('‐‐‐‐‐‐‐‐‐‐‐‐');*/ 81 82 // variables 83 var channelId = this.dataChannel.getChannelId(); 84 var linePath = d3.selectAll('svg[data="'+ channelId +'"] path[data="'+ serieId +'"].line' 85 var shapePath = d3.selectAll('svg[data="'+ channelId +'"] path[data="'+ serieId +'"].shape' 86 var lineDataPointArr = this.dataPointArr[serieId]; 87 var shapeDataPointArr = jQuery.extend(true, [], this.dataPointArr[serieId]); 88 89 // we will close up the shape downwards to enable filling properties 90 shapeDataPointArr.unshift('start'); 91 shapeDataPointArr.push('end'); 92 93 // enable or disable the stroke and fill 94 linePath.style('display', this.strokeEnabled ? 'block' : 'none'); 95 shapePath.style('display', this.fillEnabled ? 'block' : 'none'); 96 97 // check for initialize 98 if (linePath[0].length <= 0) { 99 100 // create a line path used for applying stroke 101 this.svg.append('svg:path') 102 .attr('d', this.lineGraphic([lineDataPointArr])) 103 .attr('data', serieId) 104 .attr('class', 'line'); 105 106 // create shape path used for filling 107 this.svg.append('svg:path') 108 .attr('d', this.shapeGraphic([shapeDataPointArr])) 109 .attr('data', serieId) 110 .attr('class', 'shape'); 111 } 112 113 // check for animation 114 else if (this.transitionRequested || this.transitionExecuting) { 115 116 // update the path with animation 117 linePath.data([lineDataPointArr]) 118 .transition() 119 .ease('linear') 120 .duration(this.transitionDuration) 121 .attr('d', this.lineGraphic); 122 123 // update the path with animation 124 shapePath.data([shapeDataPointArr]) 125 .transition() 126 .ease('linear') 127 .duration(this.transitionDuration) 128 .attr('d', this.shapeGraphic); 129 }

88


130 131 // otherwise, update data without animations 132 else { 133 134 // update the path without animation 135 linePath.data([lineDataPointArr]) 136 .attr('d', this.lineGraphic); 137 138 shapePath.data([shapeDataPointArr]) 139 .attr('d', this.shapeGraphic); 140 } 141 }, 142 143 /** 144 * Render the latest data point indicator 145 */ 146 renderLatestDataIndicator : function(serieId) { 147 148 // variables 149 var channelId = this.dataChannel.getChannelId(); 150 var latestIndex = this.dataPointArr[serieId].length ‐ 1; 151 //var latestIndex = this.dataChannel.getClosestIndex(serieId, this.xDomain[1]); 152 var latestDataPoint = this.dataPointArr[serieId][latestIndex]; 153 154 // calculate coordinates 155 var xCoordinate = this.getXCoordinate(serieId, latestDataPoint, latestIndex); 156 var yCoordinate = this.getYCoordinate(serieId, latestDataPoint, latestIndex); 157 158 // get the indicator for this serie 159 var indicatorCircle = d3.selectAll('svg[data="'+ channelId +'"] circle[data="'+ serieId + 160 161 // check if the indicator was already added and needs a transition 162 if (indicatorCircle[0].length <= 0) { 163 164 // create a circle 165 indicatorCircle = this.svg.append('circle') 166 .attr('r', 5) 167 .attr('data', serieId) 168 .attr('class', 'indicator'); 169 } 170 171 // move the circle to the correct position 172 indicatorCircle 173 .attr('cx', xCoordinate) 174 .attr('cy', yCoordinate); 175 }, 176 177 /** 178 * Set the axis for the x‐axis 179 */ 180 initializeXAxis : function() { 181 182 // guard: check if the axis are enabled 183 if (!this.axisEnabled) { 184 return; 185 } 186 187 // add the axis 188 this.svg.append('g') 189 .attr('class', 'axis x‐axis') 190 .attr('transform', 'translate(0, '+ this.getHeight() +')') .call(this.xAxis); 191 192 }, 193 194 /**

linegraphic.js

195 * Set the axis for the y‐axis 196 */ 197 initializeYAxis : function() { 198 199 // guard: check if the axis are enabled 200 if (!this.axisEnabled) { 201 return; 202 } 203 204 // add the axis 205 this.svg.append('g') 206 .attr('class', 'axis y‐axis') 207 //.attr('transform', 'translate('+ this.getWidth() * 1.02 +', 0)') 208 .attr('transform', 'translate(‐50, 0)') 209 .call(this.yAxis); 210 }, 211 212 /** 213 * Set the range for the y‐axis 214 */ 215 setInterpolationType : function(type) { 216 217 // set local variable 218 this.interpolationType = type; 219 220 // add interpolation to the graphic 221 this.lineGraphic.interpolate(this.interpolationType); 222 this.shapeGraphic.interpolate(this.interpolationType); 223 }, 224 });

89


1 /** 2 * A standard 2D circle graphic instance 3 */ 4 var CircleGraphic = Class.create(D3Graphic, { 5 6 /** 7 * Class ID 8 */ 9 id: 'CircleGraphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function ($super, dataChannel, options) { 15 16 // initialize parent 17 $super(dataChannel, options); 18 }, 19 20 /** 21 * Render a single serie its new data points 22 */ 23 renderSerie : function(serieId) { 24 25 // variables 26 var channelId = this.dataChannel.getChannelId(); 27 var svg = this.getSVG(); 28 var circles = d3.selectAll($(svg).find('circle[data="'+ serieId +'"]')); 29 var dataPointArr = this.dataPointArr[serieId]; 30 var originalDataPoint = dataPointArr.slice(‐1).pop(); 31 var width = $(svg).width(); 32 var height = $(svg).height(); 33 34 // TMP: apply the manipulations 35 var yCoordinate = this.getYCoordinate(serieId, originalDataPoint, dataPointArr.length ‐ 36 var dataPoint = dataPointArr.slice(‐1).pop(); 37 38 /*console.log(serieId); 39 console.log(dataPoint); 40 console.log('‐‐‐‐‐‐‐‐‐‐‐‐‐‐');*/ 41 42 // circle properties 43 var minValue = 0; 44 var maxValue = MAX_DATA_VALUE; 45 // var minValue = 25; 46 // var maxValue = 32; 47 //var minValue = serieId == 1 ? 10 : 20; 48 //var maxValue = serieId == 1 ? 110 : 32; 49 var maxRadius = (width < height ? width : height) / 2; 50 var valuePercentage = (dataPoint ‐ minValue) / (maxValue ‐ minValue); 51 var radius = maxRadius * valuePercentage; 52 var cx = width / 2; 53 var cy = height / 2; 54 var cd = parseInt(255 / maxValue * dataPoint); 55 var r = 255 ‐ (serieId == 2 ? 0 : cd); 56 var g = 255 ‐ (serieId == 2 ? cd : cd); 57 var b = 255 ‐ (serieId == 2 ? cd : 0); 58 59 // guard: check if the vlaue is valid 60 if (radius < 0) { 61 return; 62 } 63 64 // check for initialize

circlegraphic.js

65 if (circles[0].length <= 0) { 66 67 // create a line path used for applying stroke 68 this.svg.append('svg:circle') 69 .attr('data', serieId) 70 .attr('cx', cx) 71 .attr('cy', cy) 72 .attr("r", radius); 73 } 74 75 // check for animation 76 else if (this.transitionRequested) { 77 78 // update the path with animation 79 circles.transition() 80 .ease('linear') 81 .duration(this.transitionDuration) 82 .attr('cx', cx) 83 .attr('cy', cy) 84 .attr('r', radius); 85 86 // update the color 87 circles.attr('style', 'fill: '+ this.getHexColor()); 88 } 89 90 // otherwise, update data without animations 91 else { 92 93 // update the path without animation 94 circles.transition() 95 .ease('linear') 96 .duration(this.transitionDuration) 97 .attr('cx', cx) 98 .attr('cy', cy) 99 .attr('r', radius); 100 101 // update the color 102 circles.attr('style', 'fill: '+ this.getHexColor()); 103 } 104 }, 105 });

90


1 /** 2 * A real‐time 3‐axis vector 3 */ 4 var VectorGraphic = Class.create(ThreeGraphic, { 5 6 /** 7 * Class ID 8 */ 9 id: 'VectorGraphic', 10 11 /** 12 * Initialize 13 */ 14 initialize: function ($super, dataChannel, options) { 15 16 // initialize parent 17 $super(dataChannel, options); 18 19 // variables 20 var radius = 1; 21 var segments = 16; 22 var rings = 16; 23 24 // create the sphere's material 25 this.sphereMaterial = new THREE.MeshLambertMaterial({ 26 //color: 0x007095, 27 color: 0xD47616 28 //color: 0xffffff, 29 }); 30 31 // create a new mesh with sphere geometry 32 this.sphere = new THREE.Mesh( 33 new THREE.SphereGeometry( 34 radius, 35 segments, 36 rings), 37 this.sphereMaterial 38 ); 39 40 // initialize the sphere 41 this.sphere.geometry.dynamic = true; 42 43 // add the sphere to the scene 44 this.scene.add(this.sphere); 45 }, 46 47 /** 48 * Render a single serie its new data points 49 */ 50 renderSerie : function(serieId) { 51 52 // variables 53 var channelId = this.dataChannel.getChannelId(); 54 var dataPointArr = this.dataPointArr[serieId]; 55 var dataPoint = dataPointArr.slice(‐1).pop(); 56 var canvas = this.getCanvas(); 57 var width = $(canvas).width(); 58 var height = $(canvas).height(); 59 60 // circle properties 61 //var minValue = 0; 62 //var maxValue = MAX_DATA_VALUE; 63 // var minValue = 25; 64 // var maxValue = 32;

vectorgraphic.js

65 var minValue = 0; 66 var maxValue = 110; 67 var maxRadius = (width < height ? width : height) / 2; 68 var valuePercentage = (dataPoint ‐ minValue) / (maxValue ‐ minValue); 69 var radius = parseInt(maxRadius * valuePercentage) / 5; 70 var cx = width / 2; 71 var cy = height / 2; 72 73 // update the radius 74 this.sphere.position.x = 0; 75 this.sphere.position.y = 0; 76 this.sphere.scale.set(radius, radius, radius); 77 78 // rerender the scene 79 this.renderer.render(this.scene, this.camera); 80 }, 81 });

91


1 /** 2 * The Manipulation class used to handle manipulations. 3 * This class is writitng in such a way chaining of manipulations is possible. 4 */ 5 var DataManipulation = Class.create({ 6 7 /** 8 * Class ID 9 */ 10 id: 'DataManipulation', 11 12 /** 13 * Manipulation name 14 */ 15 name: 'Data Manipulation', 16 shortName: 'DM', 17 18 /** 19 * Initialize 20 */ 21 initialize: function (dataGraphic, options) { 22 23 // initialize variables 24 this.active = true; 25 this.dataGraphic = dataGraphic; 26 this.dataChannel = dataGraphic.getDataChannel(); 27 }, 28 29 /** 30 * Pre‐calculation function to check whether this manipulation is active 31 */ 32 __calculate : function(serieId, x, dataPoint, graphicIndex, dataChannelIndex) { 33 34 // guard: check if this manipulation is active 35 if (!this.active) { 36 return dataPoint; 37 } 38 39 // perform the calculation 40 return this.calculate(serieId, x, dataPoint, graphicIndex, dataChannelIndex); 41 }, 42 43 /** 44 * Apply the manipulation on a single data point 45 * NOTE: implement this in the sub‐class 46 */ 47 calculate : function(serieId, x, dataPoint, graphicIndex, dataChannelIndex) { 48 49 // default value 50 return this.dataChannel.getDataPointByX(serieId, x); 51 }, 52 53 /** 54 * Get the data channel of this manipulation 55 */ 56 getDataGraphic : function() { 57 return this.dataGraphic; 58 }, 59 60 /** 61 * Get the data channel of this manipulation 62 */ 63 getDataChannel : function() { 64 return this.dataChannel;

datamanipulation.js

65 }, 66 67 /** 68 * Check whether this manipulation is active 69 */ 70 isActive : function() { 71 return this.active; 72 }, 73 74 /** 75 * Set the active flag 76 */ 77 setActive : function(active) { 78 this.active = active; 79 }, 80 81 /** 82 * Set to inactive 83 */ 84 disable : function() { 85 this.active = false; 86 }, 87 88 /** 89 * Set to active 90 */ 91 enable : function() { 92 this.active = true; 93 }, 94 95 /** 96 * Get the parameter value 97 */ 98 getParameter : function() { 99 // implementable by child 100 }, 101 102 /** 103 * Method that is called when the parameter for calculations were changed 104 */ 105 setParameter : function(value) { 106 // implementable by child 107 }, 108 109 /** 110 * Get the prefix of the variable parameter 111 */ 112 getParameterPrefix : function() { 113 return ''; 114 }, 115 116 /** 117 * Get the prefix of the variable parameter 118 */ 119 getParameterPostfix : function() { 120 return ''; 121 }, 122 123 /** 124 * Check if this manipulation has a variable parameter 125 */ 126 hasParameter : function() { 127 return this.getParameterStepSize() !== 0; 128 }, 129

92


130 /** 131 * Get the amount of change for the parameter per pixel 132 */ 133 getParameterStepSize : function() { 134 return 0; 135 }, 136 137 /** 138 * Pre‐prepare 139 */ 140 __prepare : function() { 141 142 // guard: check if this manipulation is active 143 if (!this.active) { 144 return; 145 } 146 147 // perform the prepare 148 this.prepare(); 149 }, 150 151 /** 152 * Prepare the manipulation for a new data cycle 153 */ 154 prepare : function() { 155 // empty 156 } 157 });

datamanipulation.js

93


1 /** 2 * A manipulation that applies a differential transformation. 3 */ 4 var DifferentialManipulation = Class.create(DataManipulation, { 5 6 /** 7 * Class ID 8 */ 9 id: 'DifferentialManipulation', 10 11 /** 12 * Manipulation name 13 */ 14 name: 'Differential', 15 shortName: 'DIF', 16 17 /** 18 * Initialize 19 */ 20 initialize : function($super, dataGraphic, options) { 21 22 /** 23 * The slope is now calculated per step on the x‐axis 24 * which will normally be in milliseconds; this will multiply the slope 25 * to for example to go changes per second. 26 */ 27 this.slopeMultiplier = 1000; 28 this.previousX = 0; 29 this.previousDataPoint = 0; 30 this.lastSlope = 0; 31 32 // parent 33 $super(dataGraphic, options); 34 }, 35 36 /** 37 * Get the parameter value that can be changed in the UI 38 */ 39 getParameter : function() { 40 return this.slopeMultiplier; 41 }, 42 43 /** 44 * Method that is called when the parameter for calculations were changed 45 */ 46 setParameter : function(amount) { 47 48 // format 49 amount = parseInt(amount); 50 51 // guard: check if the amount is valid 52 if (amount <= 600) { 53 amount = 600; 54 } 55 56 this.slopeMultiplier = amount; 57 }, 58 59 /** 60 * Get the prefix of the variable parameter 61 */ 62 getParameterPrefix : function() { 63 return 'change per'; 64 },

differentialmanipulation.js

65 66 /** 67 * Get the prefix of the variable parameter 68 */ 69 getParameterPostfix : function() { 70 return 'ms'; 71 }, 72 73 /** 74 * Get the amount of change for the parameter per pixel 75 */ 76 getParameterStepSize : function() { 77 return 10; 78 }, 79 80 /** 81 * Calculate the slope in comparison with the previous data point 82 */ 83 calculate : function(serieId, x, dataPoint, graphicIndex, dataChannelIndex) { 84 85 // variables 86 var currentX = x; 87 var currentY = dataPoint; 88 var previousX = this.previousX; 89 var previousY = this.previousDataPoint; 90 /*var previousX = this.dataChannel.getPreviousXByX(serieId, currentX); 91 var previousY = this.dataGraphic.getPreviousDataPointByIndex(serieId, graphicIndex ‐ 1);*/ 92 93 // guard: check for valid x values 94 if (currentX <= 0 || previousX < 0 || currentX == previousX) { 95 return this.lastSlope; 96 } 97 98 // calculate the slope (in values per second) 99 var slope = (currentY ‐ previousY) / (currentX ‐ previousX) * this.slopeMultiplier; 100 101 // guard: check for a valid number 102 if (isNaN(slope)) { 103 104 // debug 105 /*console.log(previousY); 106 console.log(currentY); 107 console.log(currentX); 108 console.log(previousX); 109 console.log('‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐');*/ 110 return this.lastSlope; 111 } 112 113 if (dataChannelIndex === this.dataChannel.getSerieSize(serieId) ‐ 1) { 114 slope = 0; 115 } 116 117 // store this slope 118 this.lastSlope = slope; 119 120 // store the data point 121 this.previousX = x; 122 this.previousDataPoint = dataPoint; 123 124 return parseInt(slope); 125 }, 126 127 /** 128 * Reset all the properties when in a new data cycle 129 */

94


130 prepare : function() { 131 this.previousX = 0; 132 this.previousDataPoint = 0; 133 this.lastSlope = 0; 134 } 135 });

differentialmanipulation.js

95


1 /** 2 * A manipulation that applies a simple moving average. 3 */ 4 var MovingAverageManipulation = Class.create(DataManipulation, { 5 6 /** 7 * Class ID 8 */ 9 id: 'MovingAverageManipulation', 10 11 /** 12 * Manipulation name 13 */ 14 name: 'Moving Average', 15 shortName: 'MA', 16 17 /** 18 * Initialize 19 */ 20 initialize : function($super, dataGraphic, options) { 21 22 // variables 23 this.dataPointAmount = 20; 24 this.dataPointArr = []; 25 this.lastAverage = 0; 26 27 // parent 28 $super(dataGraphic, options); 29 }, 30 31 /** 32 * Get the parameter value that can be changed in the UI 33 */ 34 getParameter : function() { 35 return this.dataPointAmount; 36 }, 37 38 /** 39 * Method that is called when the parameter for calculations were changed 40 */ 41 setParameter : function(amount) { 42 43 // format 44 amount = parseInt(amount); 45 46 // guard: check if the amount is valid 47 if (amount <= 0) { 48 amount = 1; 49 } 50 51 this.dataPointAmount = amount; 52 }, 53 54 /** 55 * Get the prefix of the variable parameter 56 */ 57 getParameterPrefix : function() { 58 return 'average of'; 59 }, 60 61 /** 62 * Get the prefix of the variable parameter 63 */ 64 getParameterPostfix : function() {

movingaveragemanipulation.js

65 return ''; 66 }, 67 68 /** 69 * Get the amount of change for the parameter per pixel 70 */ 71 getParameterStepSize : function() { 72 return 0.2; 73 }, 74 75 /** 76 * Calculate the slope in comparison with the previous data point 77 */ 78 calculate : function(serieId, x, dataPoint) { 79 80 // variables 81 var lastAmount = this.dataPointArr.length; 82 var lastTotal = this.lastAverage * lastAmount; 83 var newAmount = lastAmount; 84 var newTotal = lastTotal; 85 86 // add the data point to the array 87 this.dataPointArr.push(dataPoint); 88 newAmount ++; 89 newTotal += dataPoint; 90 91 // check if the data point array is too big 92 if (this.dataPointArr.length > this.dataPointAmount) { 93 94 // pop off the data point 95 var removedDataPoint = this.dataPointArr.shift(); 96 97 // decrease new amount 98 newAmount ‐‐; 99 newTotal ‐= removedDataPoint; 100 } 101 102 // recalculate the average 103 var average = newTotal / newAmount; 104 105 // store the calculation average 106 this.lastAverage = average; 107 108 // debug 109 /*console.log(newTotal); 110 console.log(newAmount); 111 console.log(average); 112 console.log('‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐');*/ 113 114 return average; 115 }, 116 117 /** 118 * Reset all the properties when in a new data cycle 119 */ 120 prepare : function() { 121 this.dataPointArr = []; 122 this.lastAverage = 0; 123 } 124 });

96


1 /** 2 * A manipulation that converts the signal to a digital one 3 */ 4 var ThresholdManipulation = Class.create(DataManipulation, { 5 6 /** 7 * Class ID 8 */ 9 id: 'ThresholdManipulation', 10 11 /** 12 * Manipulation name 13 */ 14 name: 'Threshold', 15 shortName: 'TH', 16 17 /** 18 * Initialize 19 */ 20 initialize : function($super, dataGraphic, options) { 21 22 // variables 23 this.thresholdValue = MAX_DATA_VALUE / 2; 24 25 // parent 26 $super(dataGraphic, options); 27 }, 28 29 /** 30 * Get the parameter value that can be changed in the UI 31 */ 32 getParameter : function() { 33 return this.thresholdValue; 34 }, 35 36 /** 37 * Method that is called when the parameter for calculations were changed 38 */ 39 setParameter : function(amount) { 40 41 // format 42 amount = parseInt(amount); 43 44 // guard: check if the amount is valid 45 if (amount <= 0) { 46 amount = 1; 47 } 48 49 this.thresholdValue = amount; 50 }, 51 52 /** 53 * Get the prefix of the variable parameter 54 */ 55 getParameterPrefix : function() { 56 return 'on at'; 57 }, 58 59 /** 60 * Get the prefix of the variable parameter 61 */ 62 getParameterPostfix : function() { 63 return ''; 64 },

thresholdmanipulation.js

65 66 /** 67 * Get the amount of change for the parameter per pixel 68 */ 69 getParameterStepSize : function() { 70 return 0.8; 71 }, 72 73 /** 74 * Calculate the slope in comparison with the previous data point 75 */ 76 calculate : function(serieId, x, dataPoint) { 77 return dataPoint >= this.thresholdValue ? MAX_DATA_VALUE : MIN_DATA_VALUE; 78 } 79 });

97


1 /** 2 * A manipulation that applies a interal transformation. 3 */ 4 var IntegralManipulation = Class.create(DataManipulation, { 5 6 /** 7 * Class ID 8 */ 9 id: 'IntegralManipulation', 10 11 /** 12 * Manipulation name 13 */ 14 name: 'Integral', 15 shortName: 'INT', 16 17 /** 18 * Initialize 19 */ 20 initialize : function($super, dataGraphic, options) { 21 22 // variables 23 this.areaMultiplier = 1; 24 this.previousX = 0; 25 this.previousDataPoint = 0; 26 this.lastArea = 0; 27 28 // parent 29 $super(dataGraphic, options); 30 }, 31 32 /** 33 * Calculate the area in comparison with the previous data point 34 */ 35 calculate : function(serieId, x, dataPoint, graphicIndex, dataChannelIndex) { 36 37 // variables 38 var currentX = x; 39 var currentY = dataPoint; 40 var previousX = this.previousX; 41 var previousY = this.previousDataPoint; 42 43 // guard: check for valid x values 44 if (currentX <= 0 || previousX < 0 || currentX == previousX) { 45 return this.lastArea; 46 } 47 48 // calculate the area 49 var area = (currentX ‐ previousX) * currentY * this.areaMultiplier; 50 51 // guard: check for a valid number 52 if (isNaN(area)) { 53 return this.lastArea; 54 } 55 56 // store this area 57 this.lastArea = area; 58 59 // store the data point 60 this.previousX = x; 61 this.previousDataPoint = dataPoint; 62 63 return area; 64 }

integralmanipulation.js

98


1 /** 2 * A manipulation that inverses the data point 3 */ 4 var InverseManipulation = Class.create(DataManipulation, { 5 6 /** 7 * Class ID 8 */ 9 id: 'InverseManipulation', 10 11 /** 12 * Manipulation name 13 */ 14 name: 'Inverse', 15 shortName: 'INV', 16 17 /** 18 * Initialize 19 */ 20 initialize : function($super, dataGraphic, options) { 21 22 // parent 23 $super(dataGraphic, options); 24 }, 25 26 /** 27 * Calculate the slope in comparison with the previous data point 28 */ 29 calculate : function(serieId, x, dataPoint) { 30 31 // perform calculation 32 var inversion = MAX_DATA_VALUE ‐ dataPoint; 33 34 // return inverted 35 return inversion; 36 } 37 });

inversemanipulation.js

99


1 /** 2 * Interprets an ArrayBuffer as UTF‐8 encoded string data 3 */ 4 var byteArrayToString = function(buf) { 5 6 // variables 7 var bufView = new Uint8Array(buf); 8 var encodedString = String.fromCharCode.apply(null, bufView); 9 10 // perform decode 11 return encodedString; 12 }; 13 14 /** 15 * Converts a string to UTF‐8 encoding in a Uint8Array; returns the array buffer 16 */ 17 var stringToByteArray = function(str) { 18 19 // variables 20 /*var encodedString = unescape(encodeURIComponent(str)); 21 var bytes = new Uint8Array(encodedString.length); 22 23 // loop all the characters 24 for (var i = 0; i < encodedString.length; ++i) { 25 26 // add each character to the byte array 27 bytes[i] = encodedString.charCodeAt(i); 28 }*/ 29 30 // variables 31 var bytes = []; 32 33 for (var i = 0; i < str.length; ++i) { 34 bytes.push(str.charCodeAt(i)); 35 } 36 37 return bytes; 38 }; 39 40 /** 41 * Convert a byte array to integer 42 */ 43 var byteArrayToInt = function(byteArray) { 44 45 // variables 46 var result = ((byteArray[byteArray.length ‐ 1]) | 47 (byteArray[byteArray.length ‐ 2] << 8)); 48 49 // return the integer 50 return result; 51 }; 52 53 /** 54 * Convert a byte array to long 55 */ 56 byteArrayToLong = function(/*byte[]*/byteArray) { 57 58 // variables 59 /*var result = ((byteArray[byteArray.length ‐ 1]) | 60 (byteArray[byteArray.length ‐ 2] << 8) | 61 (byteArray[byteArray.length ‐ 3] << 16) | 62 (byteArray[byteArray.length ‐ 4] << 24));*/ 63 var result = ( 64 (byteArray[0] << 24) +

helpers.js

65 (byteArray[1] << 16) + 66 (byteArray[2] << 8) + 67 (byteArray[3]) 68 ); 69 70 // return the integer 71 return result; 72 }; 73 74 /** 75 * Generate an unique id 76 * SOURCE: https://stackoverflow.com/questions/6860853/generate‐random‐string‐for‐div‐id 77 */ 78 function generateUniqueId() { 79 var S4 = function() { 80 return (((1+Math.random())*0x10000)|0).toString(16).substring(1); 81 }; 82 return (S4()+S4()+S4()+S4()+S4()+S4()+S4()+S4()); 83 } 84 85 /** 86 * Expand date function to support 'now' 87 * SOURCE: http://stackoverflow.com/questions/221294/how‐do‐you‐get‐a‐timestamp‐in‐javascript 88 */ 89 if (!Date.now) { 90 91 // return timestamp with millis 92 Date.now = function() { return new Date().getTime(); } 93 }; 94 95 /** 96 * Get a specific value from an array 97 */ 98 getFromArray = function(key, array, defaultValue) { 99 100 // check if the array is set 101 if (!(array instanceof Array) && !(array instanceof Object)) { 102 return defaultValue; 103 } 104 105 // check if the key is set 106 if (key in array) { 107 return array[key]; 108 } 109 110 // no problem if the default value is not set 111 // we will return undefined automatically 112 return defaultValue; 113 }; 114 115 /** 116 * Set interval with a context 117 * SOURCE: http://javascriptisawesome.blogspot.nl/2011/11/setinterval‐with‐context.html 118 */ 119 setIntervalWithContext = function(code,delay,context){ 120 return setInterval(function(){ 121 code.call(context) 122 },delay); 123 }; 124 125 /** 126 * Set timeout with a context 127 * SOURCE: http://javascriptisawesome.blogspot.nl/2011/11/setinterval‐with‐context.html 128 */ 129 setTimeoutWithContext = function(code,delay,context){

100


130 return setTimeout(function(){ 131 code.call(context) 132 },delay); 133 }; 134 135 Object.size = function(obj) { 136 var size = 0, key; 137 for (key in obj) { 138 if (obj.hasOwnProperty(key)) size++; 139 } 140 return size; 141 };

helpers.js

101


appendix Data Collector and Receiver Code Please note that external libraries are not included, only code written for protoprobes is available in this appendix

102

19


103

1 /** 1 /** 2 * This sketch is the software implementation for the concept of ProtoProbes. 2 * This sketch is the software implementation for the concept of ProtoProbes. 3 * A wireless probing technology to quickly understand the sensory information of your interactive prototype. 3 * A wireless probing technology to quickly understand the sensory information of your interactive pro 4 * This ESP8266 implementation enables fast and efficient data transfers between all the client probes and the 4 * This ESP8266 implementation enables fast and efficient data transfers between all the client probes 5 * server probe. The same codebase is used for both types to enable the probe to have the ability to function as 5 * server probe. The same codebase is used for both types to enable the probe to have the ability to f 6 * both the client and the server. Also, there is a possibility to introduce a hybrid form in the future. 6 * both the client and the server. Also, there is a possibility to introduce a hybrid form in the futu 7 * 7 * 8 * CLIENT MODE: 8 * CLIENT MODE: 9 * Sends data from the analog port in an UDP packet to its host. 9 * Sends data from the analog port in an UDP packet to its host. 10 * This is done under a variable rate per second that can be changed any time. 10 * This is done under a variable rate per second that can be changed any time. 11 * 11 * 12 * SERVER MODE: 12 * SERVER MODE: 13 * Reads data from the UDP port and parses them through the serial 13 * Reads data from the UDP port and parses them through the serial 14 * as efficiently as possible to be rendered instantly on a computer. 14 * as efficiently as possible to be rendered instantly on a computer. 15 * 15 * 16 * Date: April 2016 16 * Date: April 2016 17 * Author: Pepijn Verburg 17 * Author: Pepijn Verburg 18 * Company: University of Technology in Eindhoven 18 * Company: University of Technology in Eindhoven 19 */ 19 */ 20 20 21 // library includes 21 // library includes 22 #include <Wire.h> 22 #include <Wire.h> 23 #include <ESP8266WiFi.h> 23 #include <ESP8266WiFi.h> 24 #include <WiFiUDP.h> 24 #include <WiFiUDP.h> 25 //#include <ESP8266WebServer.h> 25 //#include <ESP8266WebServer.h> 26 26 27 // Accelerometer library 27 // Accelerometer library 28 #include <Adafruit_LSM303.h> 28 #include <Adafruit_LSM303.h> 29 29 30 // NeoPixel library 30 // NeoPixel library 31 #include <NeoPixelBus.h> 31 #include <NeoPixelBus.h> 32 32 33 // SOURCE: https://github.com/esp8266/Arduino/issues/1532 33 // SOURCE: https://github.com/esp8266/Arduino/issues/1532 34 #include <Ticker.h> 34 #include <Ticker.h> 35 35 36 // easing library 36 // easing library 37 // SOURCE: http://portfolio.tobiastoft.dk/Easing‐library‐for‐Arduino 37 // SOURCE: http://portfolio.tobiastoft.dk/Easing‐library‐for‐Arduino 38 // downloaded using the web.archive.org, as download is not available anymore 38 // downloaded using the web.archive.org, as download is not available anymore 39 // CHEATSHEET: http://easings.net/nl 39 // CHEATSHEET: http://easings.net/nl 40 #include <Math.h> 40 #include <Math.h> 41 //#include <Easing.h> 41 //#include <Easing.h> 42 42 43 /** 43 /** 44 * Global constants 44 * Global constants 45 */ 45 */ 46 #define DEBUG 1 46 #define DEBUG 1 47 #define OSWATCH_RESET_TIME 5 // reboot after x seconds of nothing 47 #define OSWATCH_RESET_TIME 5 // reboot after x seconds of nothing 48 48 49 /** 49 /** 50 * Mode constants 50 * Mode constants 51 */ 51 */ 52 #define SERVER_MODE 1 52 #define SERVER_MODE 1 53 #define CLIENT_MODE 2 53 #define CLIENT_MODE 2 54 #define MODE CLIENT_MODE 54 #define MODE CLIENT_MODE 55 55 56 /** 56 /** 57 * Pin constants 57 * Pin constants 58 */ 58 */ 59 #define MAX_DIGITAL_PINS 6 // amount of digital pins 59 #define MAX_DIGITAL_PINS 6 // amount of digital pins 60 #define MAX_ANALOG_PINS 1 // amount of analog pins 60 #define MAX_ANALOG_PINS 1 // amount of analog pins 61 #define ANALOG_SENSOR_PIN A0 61 #define ANALOG_SENSOR_PIN A0 62 #define SDA_SENSOR_PIN 4 62 #define SDA_SENSOR_PIN 4 63 #define SCL_SENSOR_IN 5 63 #define SCL_SENSOR_IN 5 64 #define LIGHT_PIN 0 64 #define LIGHT_PIN 0

esp8266


65 #define MEASUREMENT_TYPE_PIN 12 66 67 /** 68 * Time constants 69 */ 70 #define ONE_SECOND 1000 // one second in milliseconds 71 72 /** 73 * Timer constants 74 */ 75 #define TIMER_LAST_TIME_INDEX 0 // index where the last time is stored 76 #define TIMER_DELAY_INDEX 1 // index of the duration we need to wait before passed 77 78 // timer definitions 79 #define PACKET_TIMER_ID 0 // id of the packet timer, NOTE: delay is handled by variable 80 #define PHYSICAL_CONTROL_TIMER_ID 1 // id of the physical control listener 81 #define PHYSICAL_CONTROL_TIMER_DELAY 500 // delay the physical control listener 82 #define ANIMATION_TIMER_ID 2 // id 83 #define ANIMATION_TIMER_DELAY 30 // delay 84 #define LED_UPDATE_TIMER_ID 3 // id 85 #define LED_UPDATE_TIMER_DELAY 20 // delay 86 #define LED_BEHAVIOUR_TIMER_ID 4 // id 87 #define LED_BEHAVIOUR_TIMER_DELAY 1000 // delay 88 #define VERIFICATION_TIMER_ID 4 // id 89 #define VERIFICATION_TIMER_DELAY 5000 // delay 90 91 /** 92 * Light constants 93 */ 94 #define NEO_PIXEL_AMOUNT 2 95 #define START_BRIGHTNESS 0 // initial brightness level of lights 96 #define MIN_BRIGHTNESS 0 // minimum brightness 97 #define MAX_BRIGHTNESS 128 // maximum brightness 98 #define STEP_BRIGHTNESS 40 // extra brightness when a step is taken 99 #define STEP_BRIGHTNESS_DURATION 100 // how long the extra brightness will stay 100 #define AVERAGE_LIGHT_SPEED 80 // an abitrary average speed 101 #define MIN_LIGHT_SPEED 30 // minimum speed of the light in brightness per second 102 #define MAX_LIGHT_SPEED 200 // maximum speed of the light in brightness per second 103 #define COLOR_CODE_BASE 0 // base number of the color codes, is 0 ok? 104 #define FADING_LIGHT_BEHAVIOUR 1 // fading LED behaviour ID 105 #define STATIC_LIGHT_BEHAVIOUR 2 // static LED behaviour ID 106 107 /** 108 * WiFi constants 109 */ 110 #define WIFI_ENABLED true 111 #define WIFI_PORT 80 112 #define WIFI_SSID "protoprobes" 113 #define WIFI_PASSWORD "protoprobes" 114 //#define WIFI_SSID "pepje" 115 //#define WIFI_PASSWORD "klaproos" 116 #define WIFI_MAX_ATTEMPTS 10 117 118 /** 119 * Serial constants 120 */ 121 #define SERIAL_BAUD_RATE 57600 122 #define SERIAL_PACKET_START "[START]" 123 #define SERIAL_PACKET_END "[END]" 124 125 /** 126 * Packet constants 127 */ 128 #define CLIENT_PACKET_FREQUENCY 20 129 #define SERVER_PACKET_FREQUENCY 20

esp8266

130 131 #define DATA_POINT_PACKET_ID 1 132 #define SET_FREQUENCY_PACKET_ID 2 133 #define DEBUG_PACKET_ID 3 134 #define MEASUREMENT_TYPE_PACKET_ID 4 135 #define SENSOR_TYPE_PACKET_ID 5 136 137 /** 138 * Animation constants 139 */ 140 #define LIGHT_ANIMATION_TYPE 2 // type of a light animation 141 #define ANIMATION_START_TIME_INDEX 0 // index in the queue array of the start time 142 #define ANIMATION_START_STATE_INDEX 1 // index in the queue array of the start value 143 #define ANIMATION_TARGET_STATE_INDEX 2 // index in the queue array of the target value 144 #define ANIMATION_DURATION_INDEX 3 // index in the queue array of the duration 145 #define ANIMATION_TYPE_INDEX 4 // index in the queue array of the duration 146 #define EASING_S 1.70158 * 2 // additional parameter needed for some easings 147 148 /** 149 * Data contants 150 */ 151 #define LONG_LENGTH 4 152 #define INT_LENGTH 2 153 #define BYTE_LENGTH 1 154 155 /** 156 * Measurement constants 157 */ 158 #define UNKNOWN_MEASUREMENT_TYPE_ID 0 159 #define ANALOG_MEASUREMENT_TYPE_ID 1 160 #define DIGITAL_MEASUREMENT_TYPE_ID 2 161 162 /** 163 * Analog constants 164 */ 165 #define MIN_ANALOG_VALUE 0 166 #define MAX_ANALOG_VALUE 1024 167 168 /** 169 * Sensor constants 170 */ 171 #define UNKNOWN_SENSOR_TYPE_ID 0 172 #define ACCELEROMETER_SENSOR_TYPE_ID 1 173 #define GYROSCOPE_SENSOR_TYPE_ID 2 174 175 /** 176 * UDP constants 177 */ 178 #define UDP_PORT 5022 // port of the UDP traffic 179 #define UDP_PACKET_SIZE 128 // maximum amount of bytes in a data packet 180 #define UDP_MAX_RETRY_AMOUNT 1 181 182 /** 183 * Server contants 184 */ 185 #define SERVER_IP_ADDRESS "192.168.4.1" // 43 186 187 /** 188 * Webserver variables 189 */ 190 //ESP8266WebServer server(WIFI_PORT); 191 192 /** 193 * UDP variables 194 */

104


105

195 WiFiUDP udp; 260 196 261 // initialize the WiFi 197 /** 262 initializeWiFi(); 198 * Watchdog timer variables 263 199 */ 264 // initialize the UDP connection 200 Ticker tickerOSWatch; 265 initializeUDP(); 201 static unsigned long last_loop; 266 202 267 // initialize the timers 203 /** 268 initializeTimers(); 204 * Sensor instances 269 205 */ 270 // initialize the sensors 206 Adafruit_LSM303 lsm; 271 initializeSensors(); 207 272 208 /** 273 // initialize all the states 209 * packet variables 274 initializeStates(); 210 */ 275 211 int packetFrequency = 0; // in Hertz 276 // initialize the lights 212 byte udpPacketBuffer[UDP_PACKET_SIZE]; 277 initializeLights(); 213 278 214 /** 279 // debug 215 * Array with specific timers 280 debug("‐‐‐‐‐‐‐‐‐‐ END BOOT SEQUENCE ‐‐‐‐‐‐‐‐‐‐"); 216 * NOTE: keep the first index in line to the amount of timers! 281 } 217 */ 282 218 long timerArr[8][2]; 283 /** 219 284 * WDT 220 /** 285 */ 221 * Animation queue of the motor and the lights 286 void initializeWatchdogTimer() { 222 */ 287 223 long animationQueueArr[MAX_DIGITAL_PINS][5]; 288 // initial update 224 289 updateWatchdogTimer(); 225 /** 290 226 * Current states of attached devices 291 // start timer 227 */ 292 tickerOSWatch.attach_ms(((OSWATCH_RESET_TIME / 3) * 1000), osWatch); 228 int stateArr[MAX_DIGITAL_PINS] = {}; 293 } 229 294 230 /** 295 /** 231 * Sensor state 296 * HYBRID: Initialize the serial 232 */ 297 */ 233 int measurementTypeId = UNKNOWN_MEASUREMENT_TYPE_ID; 298 void initializeSerial() { 234 int sensorTypeId = UNKNOWN_SENSOR_TYPE_ID; 299 235 300 // set boud rate 236 /** 301 Serial.begin(SERIAL_BAUD_RATE); 237 * Led variables 302 238 */ 303 // wait for serial attach 239 int ledBehaviour = 0; 304 while (!Serial); 240 boolean fadingDirection = false; 305 241 boolean ledsChanged = false; 306 // print new line and flush 242 NeoPixelBus strip = NeoPixelBus(NEO_PIXEL_AMOUNT, LIGHT_PIN); 307 debug(""); 243 //NeoPixelBus<NeoRgbFeature, NeoEsp8266Dma400KbpsMethod> strip(NEO_PIXEL_AMOUNT, LIGHT_PIN); 308 Serial.flush(); 244 RgbColor channelColor = RgbColor(128, 90, 0); // RgbColor(70, 31, 182); 309 245 310 // check for server 246 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 311 if (isServer()) { 247 /** INITIALIZE **/ 312 debug("‐‐‐‐‐‐‐‐‐‐ BOOTING SERVER ‐‐‐‐‐‐‐‐‐‐"); 248 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 313 } else { 249 314 debug("‐‐‐‐‐‐‐‐‐‐ BOOTING CLIENT ‐‐‐‐‐‐‐‐‐‐"); 250 /** 315 } 251 * Initialize 316 252 */ 317 // debug 253 void setup() { 318 debugInteger("The serial connection has been initialized with a baud rate of: ", SERIAL_BAUD_RATE); 254 319 } 255 // initialize WDT to prevent permanent crashes 320 256 initializeWatchdogTimer(); 321 /** 257 322 * HYBRID: Initialize the WiFi 258 // initialize the serial 323 * NOTE: we will repeat this method until we are connected 259 initializeSerial(); 324 */

esp8266


325 void initializeWiFi() { 326 327 // guard: check if we are dealing with a client 328 if (isClient()) { 329 330 // connect to the server 331 connectToServer(); 332 return; 333 } 334 335 // guard: check if we are dealing with a server 336 if (isServer()) { 337 338 // create access point 339 WiFi.softAP("protoprobes2", WIFI_PASSWORD); 340 341 // start the server 342 //server.on("/", handleHTTPRoot); 343 //server.onNotFound(handleHTTPNotFound); 344 //server.begin(); 345 346 // debug 347 debugString("Successfully created a WiFi network: ", WIFI_SSID); 348 debug("The access point IP address is:"); 349 debugIpAddress(WiFi.softAPIP()); 350 return; 351 } 352 } 353 354 /** 355 * Connect with the server probe through WiFi 356 */ 357 void connectToServer() { 358 359 // guard: check if WiFi is enabled 360 if (!WIFI_ENABLED) { 361 return; 362 } 363 364 // variables 365 int attemptCounter = 0; 366 367 // begin the WIFI 368 WiFi.begin("protoprobes2", WIFI_PASSWORD); 369 370 // wait for a valid connection 371 while (!hasWiFi()) { 372 373 // guard: check if the counter is too high 374 if (attemptCounter ++ >= WIFI_MAX_ATTEMPTS) { 375 debugString("Failed to connect to the WiFi network: ", WIFI_SSID); 376 return; 377 } 378 379 // update timer 380 updateWatchdogTimer(); 381 382 // delay process 383 writeFullLightColor(MIN_BRIGHTNESS, MIN_BRIGHTNESS, MAX_BRIGHTNESS); 384 strip.Show(); 385 delay(500); 386 writeFullLightColor(MAX_BRIGHTNESS, MIN_BRIGHTNESS, MIN_BRIGHTNESS); 387 strip.Show(); 388 delay(500); 389

esp8266

390 // debug 391 debugString("Attempting to connect to the WiFi network: ", WIFI_SSID); 392 } 393 394 // debug success 395 debugString("Successfully connected to the WiFi network: ", WIFI_SSID); 396 debug("The local IP address is: "); 397 debugIpAddress(WiFi.localIP()); 398 delay(100); 399 } 400 401 /** 402 * HYBRID: Initialize the UDP connection 403 */ 404 void initializeUDP() { 405 406 // start the UDP 407 udp.begin(UDP_PORT); 408 409 // debug 410 debug("The UDP connection has been established."); 411 debugString("Server UDP IP address: ", SERVER_IP_ADDRESS); 412 debugInteger("Server UDP port: ", UDP_PORT); 413 } 414 415 /** 416 * HYBRID: Initialize the timers 417 */ 418 void initializeTimers() { 419 420 // determine whe packet frequency 421 packetFrequency = isServer() ? SERVER_PACKET_FREQUENCY : CLIENT_PACKET_FREQUENCY; 422 423 // populate the packet timer based of the packet frequency variable 424 timerArr[PACKET_TIMER_ID][TIMER_LAST_TIME_INDEX] = 0; 425 setPacketFrequency(packetFrequency); 426 427 // populate the control timer 428 timerArr[PHYSICAL_CONTROL_TIMER_ID][TIMER_LAST_TIME_INDEX] = 0; 429 timerArr[PHYSICAL_CONTROL_TIMER_ID][TIMER_DELAY_INDEX] = PHYSICAL_CONTROL_TIMER_DELAY; 430 431 // populate animation timer 432 timerArr[ANIMATION_TIMER_ID][TIMER_LAST_TIME_INDEX] = 0; 433 timerArr[ANIMATION_TIMER_ID][TIMER_DELAY_INDEX] = ANIMATION_TIMER_DELAY; 434 435 // populate led update timer 436 timerArr[LED_UPDATE_TIMER_ID][TIMER_LAST_TIME_INDEX] = 0; 437 timerArr[LED_UPDATE_TIMER_ID][TIMER_DELAY_INDEX] = LED_UPDATE_TIMER_DELAY; 438 439 // populate led behaviour timer 440 timerArr[LED_BEHAVIOUR_TIMER_ID][TIMER_LAST_TIME_INDEX] = 0; 441 timerArr[LED_BEHAVIOUR_TIMER_ID][TIMER_DELAY_INDEX] = LED_BEHAVIOUR_TIMER_DELAY; 442 443 // debug 444 debug("The timers have been initialized."); 445 } 446 447 /** 448 * CLIENT: Initialize the sensors 449 */ 450 void initializeSensors() { 451 452 // initialize the analog pin 453 pinMode(ANALOG_SENSOR_PIN, OUTPUT); 454

106


455 // debug 520 456 debug("The sensors have been initialized."); 521 // handle sending of new packets 457 } 522 performMeasurement(); 458 523 return; 459 /** 524 } 460 * Initialize the states of certain pins 525 461 * NOTE: these pins are currently a virtual representation of the LEDs 526 // guard: check if we are in server mode 462 */ 527 if (isServer()) { 463 void initializeStates() { 528 464 529 // handle new incoming udp packets 465 // loop all the states 530 handleUDPPacket(); 466 for (int i = 0; i < MAX_DIGITAL_PINS; i++) { 531 return; 467 stateArr[i] = 0; 532 } 468 } 533 } 469 534 470 // debug 535 /** 471 debug("The states have been initialized."); 536 * HYBRID: Handle verification processes such as checking if the WiFi is still up and running 472 } 537 */ 473 538 void handleVerifications() { 474 /** 539 475 * Initialize lights 540 // guard: check if the timer has passed 476 */ 541 if (!hasPassedTimer(VERIFICATION_TIMER_ID)) { 477 void initializeLights() { 542 return; 478 543 } 479 // pin mode 544 480 pinMode(LIGHT_PIN, OUTPUT); 545 // update the timer 481 digitalWrite(LIGHT_PIN, LOW); 546 updateTimer(VERIFICATION_TIMER_ID); 482 547 483 // clear all LEDs 548 // check for client 484 writeFullLightColor(MIN_BRIGHTNESS, MIN_BRIGHTNESS, MIN_BRIGHTNESS); 549 if (isClient()) { 485 strip.Show(); 550 486 } 551 // check if there is a WiFi reconnect needed 487 552 if (!hasWiFi()) { 488 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 553 connectToServer(); 489 /** THREAD **/ 554 } else { 490 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 555 491 556 // make sure the measurement type is correct 492 /** 557 sendMeasurementTypeToUDP(); 493 * Thread 558 } 494 */ 559 } 495 void loop() { 560 496 561 // otherwise server 497 // update timer 562 else { 498 updateWatchdogTimer(); 563 // empty 499 564 } 500 // handle verifications 565 } 501 handleVerifications(); 566 502 567 /** 503 // handle new incoming serial packets 568 * HYBRID: Handle physical controls 504 handleSerialPackets(); 569 * For example to change mode 505 570 */ 506 // guard: check if we are in client mode 571 void handlePhysicalControls() { 507 if (isClient()) { 572 508 573 // guard: check if the timer has passed 509 // handle the physical controls 574 if (!hasPassedTimer(PHYSICAL_CONTROL_TIMER_ID)) { 510 handlePhysicalControls(); 575 return; 511 576 } 512 // handle the animation queue 577 513 handleAnimations(); 578 // update the timer 514 579 updateTimer(PHYSICAL_CONTROL_TIMER_ID); 515 // apply LED behaviour 580 516 applyLedBehaviour(); 581 // read the measurement type pin 517 582 int newMeasurementTypeId = UNKNOWN_MEASUREMENT_TYPE_ID; 518 // handle the NeoPixel LEDs 583 int measurementTypePinValue = digitalRead(MEASUREMENT_TYPE_PIN); 519 updateLeds(); 584

esp8266

107


585 // determine the new measurement type 586 if (measurementTypePinValue == HIGH) { 587 newMeasurementTypeId = ANALOG_MEASUREMENT_TYPE_ID; 588 } else { 589 newMeasurementTypeId = DIGITAL_MEASUREMENT_TYPE_ID; 590 } 591 592 // set to the new measurement type 593 setMeasurementType(newMeasurementTypeId); 594 } 595 596 /** 597 * Handle all the requested animations 598 */ 599 void handleAnimations() { 600 601 // check the sensor read delay 602 if (!hasPassedTimer(ANIMATION_TIMER_ID)) { 603 return; 604 } 605 606 // update the timer 607 updateTimer(ANIMATION_TIMER_ID); 608 609 // loop all the available pins 610 for (int pinId = 0; pinId < MAX_DIGITAL_PINS; pinId++) { 611 612 // guard: check if an animation is requested 613 // we can see this through a defined start time 614 if (!isAnimating(pinId)) { 615 continue; 616 } 617 618 // variables 619 long currentTime = millis(); 620 long startTime = animationQueueArr[pinId][ANIMATION_START_TIME_INDEX]; 621 float currentState = getState(pinId); 622 float startState = animationQueueArr[pinId][ANIMATION_START_STATE_INDEX]; 623 float targetState = animationQueueArr[pinId][ANIMATION_TARGET_STATE_INDEX]; 624 float duration = animationQueueArr[pinId][ANIMATION_DURATION_INDEX]; 625 int type = animationQueueArr[pinId][ANIMATION_TYPE_INDEX]; 626 long passedTime = currentTime ‐ startTime; 627 628 // guard: check if we need to remove this from the queue 629 if (passedTime >= duration) { 630 631 // disable the animation on this pin 632 resetAnimation(pinId); 633 continue; 634 } 635 636 // guard: check if we need to wait for this animation to start 637 if (passedTime <= 0) { 638 continue; 639 } 640 641 // debug 642 /*debugInteger("pin: ", pinId); 643 debugInteger("S: ", startState); 644 debugInteger("T: ", targetState); 645 debugInteger("C: ", currentState); 646 debugInteger("passed time: ", passedTime); 647 debugInteger("duration: ", duration); 648 debugInteger("type: ", type);*/ 649

esp8266

650 // check for the type, we need a special handler for this 651 // to support RGB colors 652 if (type == LIGHT_ANIMATION_TYPE) { 653 654 // variables 655 int startRed = getRed(startState); 656 int startGreen = getGreen(startState); 657 int startBlue = getBlue(startState); 658 int currentRed = getRed(currentState); 659 int currentGreen = getGreen(currentState); 660 int currentBlue = getBlue(currentState); 661 int targetRed = getRed(targetState); 662 int targetGreen = getGreen(targetState); 663 int targetBlue = getBlue(targetState); 664 int deltaRed = targetRed ‐ startRed; 665 int deltaGreen = targetGreen ‐ startGreen; 666 int deltaBlue = targetBlue ‐ startBlue; 667 int newRed = 0; 668 int newGreen = 0; 669 int newBlue = 0; 670 671 // add easing 672 newRed = easeInOutSine(passedTime, startRed, deltaRed, duration); 673 newGreen = easeInOutSine(passedTime, startGreen, deltaGreen, duration); 674 newBlue = easeInOutSine(passedTime, startBlue, deltaBlue, duration); 675 676 // validate the new states 677 newRed = validateState(newRed, pinId, type); 678 newGreen = validateState(newGreen, pinId, type); 679 newBlue = validateState(newBlue, pinId, type); 680 681 // debug 682 /*debugInteger("start red: ", startRed); 683 debugInteger("current red: ", currentRed); 684 debugInteger("target red: ", targetRed); 685 debugInteger("delta red: ", deltaRed); 686 debugInteger("new red: ", newRed); 687 debug("‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");*/ 688 689 // write the new light state 690 //writeLightColor(pinId, newRed, newGreen, newBlue); 691 } 692 693 // handling other types 694 else { 695 696 // calculate the state the device should be in 697 int deltaState = targetState ‐ startState; 698 int newState = currentState + deltaState; 699 700 // add easing 701 newState = easeInOutSine(passedTime, startState, deltaState, duration); 702 703 // validate the new state 704 newState = validateState(newState, pinId, type); 705 706 // empty for now 707 } 708 } 709 } 710 711 /** 712 * Update the LEDs if needed 713 */ 714 void updateLeds() {

108


715 716 // guard: check the timer 717 if (!hasPassedTimer(LED_UPDATE_TIMER_ID)) { 718 return; 719 } 720 721 // update the timer 722 updateTimer(LED_UPDATE_TIMER_ID); 723 724 // check if the LEDs have changed 725 if (ledsChanged) { 726 727 // update the strip 728 strip.Show(); 729 730 // disable flag 731 ledsChanged = false; 732 } 733 } 734 735 /** 736 * Handle LED behaviour 737 */ 738 void applyLedBehaviour() { 739 740 // guard: check the timer 741 if (!hasPassedTimer(LED_BEHAVIOUR_TIMER_ID)) { 742 return; 743 } 744 745 // update the timer 746 updateTimer(LED_BEHAVIOUR_TIMER_ID); 747 748 // TMP: block 749 return; 750 751 // switch on the behaviour type 752 switch (ledBehaviour) { 753 754 // static light behaviour 755 case STATIC_LIGHT_BEHAVIOUR: 756 757 // check for WiFi 758 if (!hasWiFi()) { 759 requestFullColorByTarget(MAX_BRIGHTNESS, MIN_BRIGHTNESS, MIN_BRIGHTNESS, 500); 760 } else { 761 requestFullColorByTarget(MIN_BRIGHTNESS, MAX_BRIGHTNESS, MIN_BRIGHTNESS, 500); 762 } 763 break; 764 765 // fading light behaviour 766 case FADING_LIGHT_BEHAVIOUR: 767 768 // check for direction 769 if (fadingDirection) { 770 771 // fade in 772 requestFullColorByTarget(MIN_BRIGHTNESS, MAX_BRIGHTNESS, MIN_BRIGHTNESS, 500); 773 } else { 774 775 // fade out 776 requestFullColorByTarget(MIN_BRIGHTNESS, MIN_BRIGHTNESS, MIN_BRIGHTNESS, 500); 777 } 778 779 // switch the direction

esp8266

780 fadingDirection = !fadingDirection; 781 782 // reset the LEDs 783 // other methods will update this again 784 resetLedBehaviour(); 785 break; 786 } 787 } 788 789 /** 790 * CLIENT: perform a measurment according to the current selected type 791 */ 792 void performMeasurement() { 793 794 // guard: check if the timer has passed 795 if (!hasPassedTimer(PACKET_TIMER_ID)) { 796 return; 797 } 798 799 // update the last time 800 updateTimer(PACKET_TIMER_ID); 801 802 // check which measurement type we are dealing with 803 if (isDigitalMeasurement()) { 804 performDigitalMeasurement(); 805 } else { 806 performAnalogMeasurement(); 807 } 808 } 809 810 /** 811 * CLIENT: perform a new analog read that will send a new data point 812 */ 813 void performAnalogMeasurement() { 814 815 // variables 816 byte serieId = 1; 817 long currentTime = millis(); 818 int sensorValue = analogRead(ANALOG_SENSOR_PIN); 819 int r = ((float) channelColor.R) / ((float) MAX_ANALOG_VALUE) * ((float) sensorValue) + (( 820 int g = ((float) channelColor.G) / ((float) MAX_ANALOG_VALUE) * ((float) sensorValue) + (( 821 int b = ((float) channelColor.B) / ((float) MAX_ANALOG_VALUE) * ((float) sensorValue) + (( 822 823 // write LEDs 824 writeFullLightColor(r, g, b); 825 strip.Show(); 826 827 // send a packet over the udp connection 828 sendDataPointPacketToUDP(serieId, currentTime, sensorValue); 829 } 830 831 /** 832 * CLIENT: perform a new digital nalog read that will send a new data point 833 */ 834 void performDigitalMeasurement() { 835 836 // guard: initialize the digital measurment 837 if (!lsm.begin()) { 838 return; 839 } 840 841 // perform read 842 lsm.read(); 843 /*Serial.print("Accel X: "); Serial.print((int)lsm.accelData.x); Serial.print(" "); 844 Serial.print("Y: "); Serial.print((int)lsm.accelData.y); Serial.print(" ");

109


845 Serial.print("Z: "); Serial.println((int)lsm.accelData.z); Serial.print(" "); 846 Serial.print("Mag X: "); Serial.print((int)lsm.magData.x); Serial.print(" "); 847 Serial.print("Y: "); Serial.print((int)lsm.magData.y); Serial.print(" "); 848 Serial.print("Z: "); Serial.println((int)lsm.magData.z); Serial.print(" ");*/ 849 850 // get the readings 851 int accelX = lsm.accelData.x; 852 int accelY = lsm.accelData.y; 853 int accelZ = lsm.accelData.z; 854 int magX = lsm.magData.x; 855 int magY = lsm.magData.y; 856 int magZ = lsm.magData.z; 857 int totalAccel = abs(accelX) + abs(accelY) + abs(accelZ) ‐ 3000; 858 int maxAccel = 8000; 859 860 // variables 861 long currentTime = millis(); 862 863 // calculate LEDs 864 int r = ((float) channelColor.R) / ((float) maxAccel) * ((float) totalAccel) + ((float) channelColor.R / 865 int g = ((float) channelColor.G) / ((float) maxAccel) * ((float) totalAccel) + ((float) channelColor.G / 866 int b = ((float) channelColor.B) / ((float) maxAccel) * ((float) totalAccel) + ((float) channelColor.B / 867 868 // write LEDs 869 writeFullLightColor(r, g, b); 870 strip.Show(); 871 872 // send a packet over the udp connection 873 sendDataPointPacketToUDP(1, currentTime, accelX); 874 sendDataPointPacketToUDP(2, currentTime, accelY); 875 sendDataPointPacketToUDP(3, currentTime, accelZ); 876 } 877 878 /** 879 * SERVER: read new udp packets containing a new data point 880 */ 881 void handleUDPPacket() { 882 883 // guard: check if the timer has passed 884 if (!hasPassedTimer(PACKET_TIMER_ID)) { 885 return; 886 } 887 888 // update the timer 889 updateTimer(PACKET_TIMER_ID); 890 891 // read the number of available bytes 892 int packetSize = udp.parsePacket(); 893 894 // guard: check if there is a packet available 895 if (packetSize <= 0) { 896 return; 897 } 898 899 // variables 900 byte channelId = 1; 901 902 // debug 903 debug(""); 904 debugInteger("Reading a new UDP packet with size: ", packetSize); 905 906 // read the UDP packet and populate the UDP packet buffer 907 readPacketFromUDP(packetSize); 908 909 // send the packet to the serial

esp8266

910 sendPacketToSerial(channelId, udpPacketBuffer, packetSize); 911 912 // flush the udp channel 913 // if data is lost the server is simply too slow 914 // TODO: implement a check that can identify such a situation? 915 udp.flush(); 916 } 917 918 /** 919 * Handle new incoming serial packets 920 */ 921 void handleSerialPackets() { 922 923 924 // guard: check for available 925 if (Serial.available() < 1) { 926 return; 927 } 928 929 // perform the read 930 uint8_t packetId = Serial.read(); 931 932 // debug 933 debugInteger("New packet received: ", packetId); 934 935 // switch on the packet 936 // NOTE: the +48 cases are added to support 937 // direct commands from the serial monitor 938 switch (packetId) { 939 940 // serial debugger 941 case DEBUG_PACKET_ID: 942 case DEBUG_PACKET_ID + 48: 943 Serial.println(WiFi.localIP()); 944 break; 945 946 // change the packet frequency 947 case SET_FREQUENCY_PACKET_ID: 948 case SET_FREQUENCY_PACKET_ID + 48: 949 950 // variables 951 int newFrequency = (int) (waitForSignedByte() + 128); 952 953 // set the new packet frequency 954 setPacketFrequency(newFrequency); 955 break; 956 } 957 } 958 959 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 960 /** WEBSERVER 961 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 962 963 /** 964 * SERVER: Handle any HTTP request on the root directory 965 */ 966 void handleHTTPRoot() { 967 //server.send(200, "text/html", "<h1>Protoprobe server is running!</h1>"); 968 } 969 970 /** 971 * SERVER: Handle any HTTP request that were invalid 972 */ 973 void handleHTTPNotFound() { 974 //server.send(200, "text/html", "<h1>This protoprobe page is not found!</h1>");

110


111

1040 Serial.println(byteArrayToLong(timeBuffer)); 975 } 1041 Serial.println(byteArrayToInt(valueBuffer)); 976 1042 Serial.println("‐‐‐‐‐‐‐‐‐‐‐‐‐ END UDP PACKET ‐‐‐‐‐‐‐‐‐‐‐‐‐"); 977 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 978 /** UDP **/ 1043 }*/ 979 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1044 } 980 1045 981 /** 1046 /** 982 * CLIENT: Send a new data point over UDP 1047 * CLIENT: Send the current measurement type to the server 983 * long to byte array source: http://forum.arduino.cc/index.php?topic=42158.0 1048 */ 984 */ 1049 void sendMeasurementTypeToUDP() { 985 void sendDataPointPacketToUDP(byte serieId, long currentTime, int value) { 1050 986 1051 // as we are dealing with UDP we send it a couple of times 987 // variables 1052 for (int i = 0; i < UDP_MAX_RETRY_AMOUNT; i++) { 988 uint8_t packetSize = BYTE_LENGTH + BYTE_LENGTH + LONG_LENGTH + INT_LENGTH; 1053 sendBytePacketToUDP(MEASUREMENT_TYPE_PACKET_ID, measurementTypeId); 989 uint8_t packetBuffer[packetSize]; 1054 delay(10); 990 1055 } 991 // add the packet ID 1056 } 992 packetBuffer[0] = (uint8_t) DATA_POINT_PACKET_ID; 1057 993 1058 /** 994 // add the serie id 1059 * CLIENT: Send the current sensor type to the server 995 packetBuffer[1] = (uint8_t) serieId; 1060 */ 996 1061 void sendSensorTypeToUDP() { 997 // add the time 1062 998 packetBuffer[2] = (uint8_t)((currentTime >> 24) & 0xff); 1063 // as we are dealing with UDP we send it a couple of times 999 packetBuffer[3] = (uint8_t)((currentTime >> 16) & 0xff); 1064 for (int i = 0; i < UDP_MAX_RETRY_AMOUNT; i++) { 1000 packetBuffer[4] = (uint8_t)((currentTime >> 8) & 0xff); 1065 sendBytePacketToUDP(SENSOR_TYPE_PACKET_ID, measurementTypeId); 1001 packetBuffer[5] = (uint8_t)((currentTime >> 0) & 0xff); 1066 delay(10); 1002 1067 } 1003 // add the value 1068 } 1004 packetBuffer[6] = (uint8_t)((value >> 8) & 0xff); 1069 1005 packetBuffer[7] = (uint8_t)((value >> 0) & 0xff); 1070 /** 1006 1071 * CLIENT: Send the current measurement type to the server 1007 // start the packet 1072 */ 1008 udp.beginPacket(SERVER_IP_ADDRESS, UDP_PORT); 1073 void sendBytePacketToUDP(byte packetId, byte value) { 1009 1074 1010 // write the time of measurement 1075 // variables 1011 udp.write(packetBuffer, packetSize); 1076 uint8_t packetSize = BYTE_LENGTH + BYTE_LENGTH; 1012 1077 uint8_t packetBuffer[packetSize]; 1013 // close the packet 1078 1014 udp.endPacket(); 1079 // set the packet ID 1015 1080 packetBuffer[0] = packetId; 1016 // let the light blink 1081 1017 debug("data"); 1082 // add the measurment type id 1018 1083 packetBuffer[1] = value; 1019 // perform converts 1084 1020 /*if (DEBUG == 1) { 1085 // start the packet 1021 byte packetIdBuffer; 1086 udp.beginPacket(SERVER_IP_ADDRESS, UDP_PORT); 1022 byte serieIdBuffer; 1087 1023 uint8_t timeBuffer[LONG_LENGTH]; 1088 // write the time of measurement 1024 uint8_t valueBuffer[INT_LENGTH]; 1089 udp.write(packetBuffer, packetSize); 1025 packetIdBuffer = packetBuffer[0]; 1090 1026 serieIdBuffer = packetBuffer[1]; 1091 // close the packet 1027 timeBuffer[0] = packetBuffer[2]; 1092 udp.endPacket(); 1028 timeBuffer[1] = packetBuffer[3]; 1093 } 1029 timeBuffer[2] = packetBuffer[4]; 1094 1030 timeBuffer[3] = packetBuffer[5]; 1095 /** 1031 valueBuffer[0] = packetBuffer[6]; 1096 * SERVER: Read a packet over UDP to prepare the read buffer 1032 valueBuffer[1] = packetBuffer[7]; 1097 */ 1033 1098 void readPacketFromUDP(int packetSize) { 1034 // debug 1099 1035 Serial.println("‐‐‐‐‐‐‐‐‐‐‐‐‐ START UDP PACKET ‐‐‐‐‐‐‐‐‐‐‐‐‐"); 1100 // populate the buffer with the amount of available bytes 1036 Serial.println(SERVER_IP_ADDRESS); 1101 udp.read(udpPacketBuffer, packetSize); 1037 Serial.println(UDP_PORT); 1102 } 1038 Serial.println(packetIdBuffer); 1103 1039 Serial.println(serieIdBuffer); 1104 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐

esp8266


112

1105 /** SERIAL **/ 1170 1106 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1171 // guard: check if the max attempts were exceeded 1107 1172 if (attempts ++ >= maxAttempts) { 1108 /** 1173 return 0; 1109 * SERVER: Send an array of bytes over the serial 1174 } 1110 */ 1175 1111 void sendPacketToSerial(byte channelId, byte* data, int length) { 1176 // delay 1112 1177 delay(intervalTime); 1113 // start packet 1178 } 1114 Serial.print(SERIAL_PACKET_START); 1179 1115 1180 // a byte is available, return it at once 1116 // second byte: identify the channel 1181 return Serial.read(); 1117 Serial.write(channelId); 1182 } 1118 1183 1119 // last bytes: write the packet contents 1184 1120 Serial.write(data, length); 1185 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1121 1186 /** ANIMATION 1122 // end packet 1187 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1123 Serial.print(SERIAL_PACKET_END); 1188 1124 1189 /** 1125 // debug 1190 * Request an animation 1126 /*if (DEBUG == 1) { 1191 * Speed is in value per second 1127 1192 */ 1128 // variables 1193 void requestAnimationByTarget(int pinId, int targetState, int speed, int type) { 1129 byte serieId; 1194 1130 byte timeArr[LONG_LENGTH]; 1195 // variables 1131 byte valueArr[INT_LENGTH]; 1196 long currentTime = millis(); 1132 1197 int currentState = getState(pinId); 1133 // populate the arrays 1198 int duration = ((float) abs(targetState ‐ currentState)) / ((float) speed) * 1000; 1134 serieId = dataPoint[0]; 1199 1135 timeArr[0] = dataPoint[1]; 1200 // check if the duration should be modified as we are dealing with and RGB value 1136 timeArr[1] = dataPoint[2]; 1201 if (type == LIGHT_ANIMATION_TYPE) { 1137 timeArr[2] = dataPoint[3]; 1202 1138 timeArr[3] = dataPoint[4]; 1203 // TMP: take the speed as the duration 1139 valueArr[0] = dataPoint[5]; 1204 duration = speed; 1140 valueArr[1] = dataPoint[6]; 1205 } 1141 1206 1142 // convert to readable data 1207 // debug 1143 long currentTime = byteArrayToLong(timeArr); 1208 /*debugInteger("requested pin: ", pinId); 1144 long value = byteArrayToInt(valueArr); 1209 debugInteger("requested time: ", currentTime); 1145 1210 debugInteger("requested state: ", targetState); 1146 // debug 1211 debugInteger("requested duration: ", duration);*/ 1147 debug("‐‐‐‐‐‐‐‐‐‐‐‐‐‐ NEW DATA POINT ‐‐‐‐‐‐‐‐‐‐‐‐‐‐"); 1212 1148 debugByte("Serie ID: ", serieId); 1213 // add this target to the movement queue 1149 debugLong("Time: ", currentTime); 1214 animationQueueArr[pinId][ANIMATION_START_TIME_INDEX] = currentTime; 1150 debugInteger("Value: ", value); 1215 animationQueueArr[pinId][ANIMATION_START_STATE_INDEX] = currentState; 1151 debug("‐‐‐‐‐‐‐‐‐‐‐‐‐‐ END OF DATA POINT ‐‐‐‐‐‐‐‐‐‐‐‐‐‐"); 1216 animationQueueArr[pinId][ANIMATION_TARGET_STATE_INDEX] = targetState; 1152 }*/ 1217 animationQueueArr[pinId][ANIMATION_DURATION_INDEX] = duration; 1153 } 1218 animationQueueArr[pinId][ANIMATION_TYPE_INDEX] = type; 1154 1219 } 1155 /** 1220 1156 * Wait for a single byte on the Serial 1221 /** 1157 */ 1222 * Request a specific movement of a motor in relation to its current location 1158 int8_t waitForSignedByte() { 1223 * NOTE: distance in degrees and speed in degrees per second 1159 1224 */ 1160 // variables 1225 void requestAnimationByDelta(int pinId, int deltaState, int speed, int type) { 1161 int attempts = 0; 1226 1162 1227 // variables 1163 // error cathing when no bytes appear 1228 int currentState = getState(pinId); 1164 // now on a maximum of 2500ms 1229 int targetState = currentState + deltaState; 1165 int maxAttempts = 50; 1230 1166 int intervalTime = 50; 1231 // finally request movement by target 1167 1232 requestAnimationByTarget(pinId, targetState, speed, type); 1168 // wait until a byte is available 1233 } 1169 while (Serial.available() <= 0) { 1234

esp8266


1235 /** 1300 setState(lightPin, constructColorCode(r, g, b)); 1236 * Request a light color by target 1301 1237 */ 1302 // write the light 1238 void requestColorByTarget(int lightPin, int red, int green, int blue, int duration) { 1303 strip.SetPixelColor(lightPin, RgbColor(r, g, b)); 1239 requestAnimationByTarget(lightPin, constructColorCode(red, green, blue), duration, LIGHT_ANIMATION_TYPE); 1304 1240 } 1305 // set changed flag 1241 1306 ledsChanged = true; 1242 /** 1307 } 1243 * Request a light color by target for the whole strip 1308 1244 */ 1309 /** 1245 void requestFullColorByTarget(int red, int green, int blue, int duration) { 1310 * Construct a color code formatted as long based on rgb values 1246 1311 */ 1247 // loop all the neo pixels 1312 long constructColorCode(int r, int g, int b) { 1248 for (int i = 0; i < NEO_PIXEL_AMOUNT; i++) { 1313 1249 requestAnimationByTarget(i, constructColorCode(red, green, blue), duration, LIGHT_ANIMATION_TYPE); 1314 // we start the base of with 1 billion 1250 } 1315 long colorCode = COLOR_CODE_BASE; 1251 } 1316 1252 1317 // red takes up the first three zero's 1253 /** 1318 colorCode += r * 1000000; 1254 * Request a light color by delta 1319 1255 */ 1320 // green takes up the second three zero's 1256 void requestColorByDelta(int lightPin, int red, int green, int blue, int duration) { 1321 colorCode += g * 1000; 1257 requestAnimationByTarget(lightPin, getState(lightPin) + constructColorCode(red, green, blue), duration, LIGHT_ANIMATION_TYPE); 1322 1258 } 1323 // blue takes up the last three zero's 1259 1324 colorCode += b * 1; 1260 /** 1325 1261 * Check whether a movement is queued for a specific motor 1326 return colorCode; 1262 */ 1327 } 1263 boolean isAnimating(int pinId) { 1328 1264 1329 /** 1265 // get the start time, this variables is 0 when nothing is queued 1330 * Get the red component of a color code 1266 long startTime = animationQueueArr[pinId][ANIMATION_START_TIME_INDEX]; 1331 */ 1267 1332 int getRed(long colorCode) { 1268 // guard: check the start time 1333 1269 if (startTime <= 0) { 1334 // substract base code and fetch the red 1270 return false; 1335 return (int) ((colorCode ‐ COLOR_CODE_BASE) / 1000000); 1271 } 1336 } 1272 1337 1273 return true; 1338 /** 1274 } 1339 * Get the green component of a color code 1275 1340 */ 1276 /** 1341 int getGreen(long colorCode) { 1277 * Reset the animation for a specific pin 1342 1278 */ 1343 // variables 1279 void resetAnimation(int pinId) { 1344 int red = getRed(colorCode); 1280 animationQueueArr[pinId][ANIMATION_START_TIME_INDEX] = 0; 1345 1281 } 1346 // substract base code and fetch the red 1282 1347 return (int) ((colorCode ‐ COLOR_CODE_BASE ‐ red * 1000000) / 1000); 1283 /** 1348 } 1284 * Write color for all LEDs 1349 1285 */ 1350 /** 1286 void writeFullLightColor(int r, int g, int b) { 1351 * Get the green component of a color code 1287 1352 */ 1288 // loop all the neo pixels 1353 int getBlue(long colorCode) { 1289 for (int i = 0; i < NEO_PIXEL_AMOUNT; i++) { 1354 1290 writeLightColor(i, r, g, b); 1355 // variables 1291 } 1356 int red = getRed(colorCode); 1292 } 1357 int green = getGreen(colorCode); 1293 1358 1294 /** 1359 // substract base code and fetch the red 1295 * Write a specific color 1360 return (int) ((colorCode ‐ COLOR_CODE_BASE ‐ red * 1000000 ‐ green * 1000) / 1); 1296 */ 1361 } 1297 void writeLightColor(int lightPin, int r, int g, int b) { 1362 1298 1363 /** 1299 // set the state 1364 * Validate a state by its type

esp8266

113


114

1365 * We will check the minimum and maximum value 1430 1366 */ 1431 /** 1367 int validateState(int state, int pinId, int type) { 1432 * Set to either unknown / analog / digital measurement and send an update to the UI 1368 1433 */ 1369 // check type for minimum, maximum and duration 1434 void setMeasurementType(int typeId) { 1370 if (type == LIGHT_ANIMATION_TYPE) { 1435 1371 1436 // guard: check if the type is the same 1372 // check min brightness 1437 if (measurementTypeId == typeId) { 1373 if (state < MIN_BRIGHTNESS) { 1438 return; 1374 state = MIN_BRIGHTNESS; 1439 } 1375 } 1440 1376 1441 // debug 1377 // check max brightness 1442 debugInteger("The new measurement type ID is: ", typeId); 1378 if (state > MAX_BRIGHTNESS) { 1443 1379 state = MAX_BRIGHTNESS; 1444 // update the global variable 1380 } 1445 measurementTypeId = typeId; 1381 } 1446 1382 1447 // send the change to the server 1383 return state; 1448 sendMeasurementTypeToUDP(); 1384 } 1449 } 1385 1450 1386 /** 1451 /** 1387 * Set the state of a device 1452 * Reset the LED behaviour 1388 */ 1453 */ 1389 void setState(int pinId, int state) { 1454 void resetLedBehaviour() { 1390 stateArr[pinId] = state; 1455 ledBehaviour = 0; 1391 } 1456 } 1392 1457 1393 /** 1458 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1394 * Get the state of a device 1459 /** CHECKERS 1395 */ 1460 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1396 int getState(int pinId) { 1461 1397 return stateArr[pinId]; 1462 /** 1398 } 1463 * Check whether this is a server probe 1399 1464 */ 1400 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1465 boolean isServer() { 1401 /** EASING **/ 1466 return MODE == SERVER_MODE; 1402 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1467 } 1403 1468 1404 // sinusoidal easing in/out ‐ accelerating until halfway, then decelerating 1469 /** 1405 float easeInOutSine (float t, float b, float c, float d) { 1470 * Check whether this is a client probe 1406 return ‐c/2 * (cos(M_PI*t/d) ‐ 1) + b; 1471 */ 1407 } 1472 boolean isClient() { 1408 1473 return MODE == CLIENT_MODE; 1409 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1474 } 1410 /** SETTERS **/ 1475 1411 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1476 /** 1412 1477 * Check if we have a valid WiFi connection 1413 /** 1478 */ 1414 * Set the frequency on which the data is read 1479 boolean hasWiFi() { 1415 */ 1480 return WiFi.status() == WL_CONNECTED; 1416 void setPacketFrequency(int frequency) { 1481 } 1417 1482 1418 // set the frequency 1483 /** 1419 packetFrequency = frequency; 1484 * Check whether reading from a digital sensor 1420 1485 */ 1421 // calculate the new delay 1486 boolean isDigitalMeasurement() { 1422 int packetDelay = ((float) ONE_SECOND) / ((float) packetFrequency); 1487 return measurementTypeId == DIGITAL_MEASUREMENT_TYPE_ID; 1423 1488 } 1424 // update the timer delay 1489 1425 timerArr[PACKET_TIMER_ID][TIMER_DELAY_INDEX] = packetDelay; 1490 /** 1426 1491 * Check whether we are measuring the analog port 1427 // debug 1492 */ 1428 debugInteger("The new packet delay is: ", packetDelay); 1493 boolean isAnalogMeasurement() { 1429 } 1494 return measurementTypeId == ANALOG_MEASUREMENT_TYPE_ID;

esp8266


115

1495 } 1560 /** DATA 1496 1561 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1497 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1562 1498 /** TIMERS **/ 1563 /** 1499 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/ 1564 * Convert a long to an array of bytes 1500 1565 */ 1501 /** 1566 byte* longToByteArray(long value) { 1502 * Get a last timer value 1567 1503 */ 1568 // variables 1504 long *getTimer(int id) { 1569 byte longBuffer[LONG_LENGTH]; 1505 return timerArr[id]; 1570 1506 } 1571 // populate the buffer 1507 1572 longBuffer[0] = (int)((value >> 24) & 0xFF); 1508 /** 1573 longBuffer[1] = (int)((value >> 16) & 0xFF); 1509 * Check whether the timer value has passed 1574 longBuffer[2] = (int)((value >> 8) & 0XFF); 1510 */ 1575 longBuffer[3] = (int)((value & 0XFF)); 1511 boolean hasPassedTimer(int id) { 1576 1577 return longBuffer; 1512 1578 } 1513 // variables 1579 1514 long *timerProperties = getTimer(id); 1580 /** 1515 long lastTime = timerProperties[TIMER_LAST_TIME_INDEX]; 1581 * Convert array of bytes to a long 1516 1582 */ 1517 // check if the last time is set 1583 long byteArrayToLong(byte* byteArray) { 1518 if (lastTime <= 0) { 1584 1519 return true; 1585 // convert to the long 1520 } 1586 long value = (((unsigned long)byteArray[0] << 24) + ((unsigned long)byteArray[1] << 16) + (( 1521 1587 return value; 1522 return millis() >= (lastTime + timerProperties[TIMER_DELAY_INDEX]); 1588 } 1523 } 1589 1524 1590 /** 1525 /** 1591 * Convert an integer to an array of bytes 1526 * Update the timer to the current millis 1592 */ 1527 */ 1593 byte* intToByteArray(int value) { 1528 void updateTimer(int id) { 1594 1529 setTimer(id, millis()); 1595 // variables 1530 } 1596 byte intBuffer[INT_LENGTH]; 1531 1597 1532 /** 1598 // populate the buffer 1533 * Set a timer to a specific value 1599 intBuffer[0] = (byte) value; 1534 */ 1600 intBuffer[1] = (byte) value >> 8; 1535 void setTimer(int id, long time) { 1601 1536 timerArr[id][TIMER_LAST_TIME_INDEX] = time; 1602 return intBuffer; 1537 } 1603 } 1538 1604 1539 /** 1605 /** 1540 * Watchdog timer 1606 * Convert array of bytes to an integer 1541 */ 1607 */ 1542 void ICACHE_RAM_ATTR osWatch(void) { 1608 int byteArrayToInt(byte* byteArray) { 1543 unsigned long t = millis(); 1609 1544 unsigned long last_run = abs(t ‐ last_loop); 1610 // convert to the integer 1545 if(last_run >= (OSWATCH_RESET_TIME * 1000)) { 1611 int value = (unsigned long)(byteArray[0] << 8) | byteArray[1]; 1546 // save the hit here to eeprom or to rtc memory if needed 1612 return value; 1547 ESP.restart(); // normal reboot 1613 } 1548 //ESP.reset(); // hard reset 1614 1549 } 1615 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1550 } 1616 /** DEBUG 1551 1617 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1552 /** 1618 1553 * Update the WDT 1619 /** 1554 */ 1620 * Debug two strings 1555 void updateWatchdogTimer() { 1621 */ 1556 last_loop = millis(); 1622 void debugString(String message, String message2) { 1557 } 1623 1558 1624 // guard: check if debugging 1559 /**‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐**/

esp8266


1625 if (DEBUG == 0) { 1626 return; 1627 } 1628 1629 // print 1630 Serial.print(message); 1631 Serial.println(message2); 1632 } 1633 1634 /** 1635 * Debug an ip address object 1636 */ 1637 void debugIpAddress(IPAddress ipAddress) { 1638 1639 // guard: check if debugging 1640 if (DEBUG == 0) { 1641 return; 1642 } 1643 1644 // print 1645 Serial.println(ipAddress); 1646 } 1647 1648 /** 1649 * Debug a message with a variable 1650 */ 1651 void debugInteger(String message, int value) { 1652 1653 // guard: check if debugging 1654 if (DEBUG == 0) { 1655 return; 1656 } 1657 1658 // send the message 1659 Serial.print(message); 1660 1661 // send the variable 1662 Serial.println(value); 1663 } 1664 1665 /** 1666 * Debug a long 1667 */ 1668 void debugByte(String message, byte value) { 1669 1670 // guard: check if debugging 1671 if (DEBUG == 0) { 1672 return; 1673 } 1674 1675 // send the message 1676 Serial.print(message); 1677 1678 // send the variable 1679 Serial.println(value); 1680 } 1681 1682 /** 1683 * Debug a long 1684 */ 1685 void debugLong(String message, long value) { 1686 1687 // guard: check if debugging 1688 if (DEBUG == 0) { 1689 return;

esp8266

1690 } 1691 1692 // send the message 1693 Serial.print(message); 1694 1695 // send the variable 1696 Serial.println(value); 1697 } 1698 1699 /** 1700 * Debug a message with a variable 1701 */ 1702 void forceDebugInteger(String message, int value) { 1703 1704 // send the message 1705 Serial.print(message); 1706 1707 // send the variable 1708 Serial.println(value); 1709 } 1710 1711 /** 1712 * Debug a message 1713 */ 1714 void debug(String message) { 1715 1716 // guard: check if debugging 1717 if (DEBUG == 0) { 1718 return; 1719 } 1720 1721 // send the message 1722 Serial.println(message); 1723 }

116


117



Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.