diff --git a/Arduino/MassiveKnob/MassiveKnob.ino b/Arduino/MassiveKnob/MassiveKnob.ino
index d59e095..291bc8e 100644
--- a/Arduino/MassiveKnob/MassiveKnob.ino
+++ b/Arduino/MassiveKnob/MassiveKnob.ino
@@ -5,31 +5,31 @@
*
*/
// Set this to the number of potentiometers you have connected
-const byte AnalogInputCount = 1;
+const byte AnalogInputCount = 3;
// Set this to the number of buttons you have connected
-const byte DigitalInputCount = 1;
+const byte DigitalInputCount = 0;
// Not supported yet - maybe PWM and/or other means of analog output?
const byte AnalogOutputCount = 0;
// Set this to the number of digital outputs you have connected
-const byte DigitalOutputCount = 1;
+const byte DigitalOutputCount = 0;
// For each potentiometer, specify the pin
const byte AnalogInputPin[AnalogInputCount] = {
- A0
+ A0,
+ A1,
+ A2
};
// For each button, specify the pin. Assumes pull-up.
const byte DigitalInputPin[DigitalInputCount] = {
- 3
};
// For each digital output, specify the pin
const byte DigitalOutputPin[DigitalOutputCount] = {
- 2
};
@@ -42,7 +42,12 @@ const float EMAAlpha = 0.6;
// How many measurements to take at boot time for analog inputs to seed the EMA
const byte EMASeedCount = 5;
+// Minimum treshold for reporting changes in analog values, reduces noise left over from the EMA. Note that once an analog value
+// changes beyond the treshold, that input will report all changes until the FocusTimeout has expired to avoid losing accuracy.
+const byte AnalogTreshold = 2;
+// How long to ignore other inputs after an input changes. Reduces noise due voltage drops.
+const unsigned long FocusTimeout = 100;
/*
@@ -51,6 +56,12 @@ const byte EMASeedCount = 5;
* Here be dragons.
*
*/
+
+// If defined, only outputs will be sent to the serial port as Arduino Plotter compatible data
+//#define DebugOutputPlotter
+
+
+#ifndef DebugOutputPlotter
#include "./min.h"
#include "./min.c"
@@ -77,6 +88,7 @@ const uint8_t FrameIDAnalogOutput = 3;
const uint8_t FrameIDDigitalOutput = 4;
const uint8_t FrameIDQuit = 62;
const uint8_t FrameIDError = 63;
+#endif
struct AnalogInputStatus
@@ -94,7 +106,6 @@ struct DigitalInputStatus
};
-bool active = false;
struct AnalogInputStatus analogInputStatus[AnalogInputCount];
struct DigitalInputStatus digitalInputStatus[AnalogInputCount];
@@ -107,8 +118,10 @@ void setup()
while (!Serial) {}
+ #ifndef DebugOutputPlotter
// Set up the MIN protocol (http://github.com/min-protocol/min)
min_init_context(&minContext, 0);
+ #endif
// Seed the moving average for analog inputs
@@ -147,12 +160,39 @@ void setup()
}
+#ifdef DebugOutputPlotter
+unsigned long lastOutput = 0;
+#endif
+
+enum FocusType
+{
+ FocusTypeNone = 0,
+ FocusTypeAnalogInput = 1,
+ FocusTypeDigitalInput = 2,
+ FocusTypeOutput = 3
+};
+
+bool active = false;
+FocusType focusType = FocusTypeNone;
+byte focusInputIndex;
+unsigned long focusOutputTime;
+
+#define IsAnalogInputFocus(i) ((focusType == FocusInputType.AnalogInput) && (focusInputIndex == i))
+#define IsDigitalInputFocus(i) ((focusType == FocusInputType.DigitalInput) && (focusInputIndex == i))
+
+
void loop()
{
+ #ifndef DebugOutputPlotter
char readBuffer[32];
size_t readBufferSize = Serial.available() > 0 ? Serial.readBytes(readBuffer, 32U) : 0;
min_poll(&minContext, (uint8_t*)readBuffer, (uint8_t)readBufferSize);
+ #endif
+
+
+ if (focusType == FocusTypeOutput && millis() - focusOutputTime >= FocusTimeout)
+ focusType = FocusTypeNone;
// Check analog inputs
@@ -160,8 +200,32 @@ void loop()
for (byte i = 0; i < AnalogInputCount; i++)
{
newAnalogValue = getAnalogValue(i);
+ bool changed;
- if (newAnalogValue != analogInputStatus[i].Value && (millis() - analogInputStatus[i].LastChange >= MinimumInterval))
+ switch (focusType)
+ {
+ case FocusTypeAnalogInput:
+ if (focusInputIndex != i)
+ continue;
+
+ if (millis() - analogInputStatus[i].LastChange < FocusTimeout)
+ {
+ changed = newAnalogValue != analogInputStatus[i].Value;
+ break;
+ }
+ else
+ focusType = FocusTypeNone;
+ // fall-through
+
+ case FocusTypeNone:
+ changed = abs(analogInputStatus[i].Value - newAnalogValue) >= AnalogTreshold;
+ break;
+
+ default:
+ continue;
+ }
+
+ if (changed && (millis() - analogInputStatus[i].LastChange >= MinimumInterval))
{
if (active)
// Send out new value
@@ -179,6 +243,23 @@ void loop()
{
newDigitalValue = getDigitalValue(i);
+ switch (focusType)
+ {
+ case FocusTypeAnalogInput:
+ case FocusTypeOutput:
+ continue;
+
+ case FocusTypeDigitalInput:
+ if (focusInputIndex != i)
+ continue;
+
+ if (millis() - digitalInputStatus[i].LastChange >= FocusTimeout)
+ focusType = FocusTypeNone;
+
+ break;
+ }
+
+
if (newDigitalValue != digitalInputStatus[i].Value && (millis() - digitalInputStatus[i].LastChange >= MinimumInterval))
{
if (active)
@@ -189,9 +270,33 @@ void loop()
digitalInputStatus[i].LastChange = millis();
}
}
+
+ #ifdef DebugOutputPlotter
+ if (millis() - lastOutput >= 100)
+ {
+ for (byte i = 0; i < AnalogInputCount; i++)
+ {
+ if (i > 0)
+ Serial.print("\t");
+
+ Serial.print(analogInputStatus[i].Value);
+ }
+
+ for (byte i = 0; i < DigitalInputCount; i++)
+ {
+ if (i > 0 || AnalogInputCount > 0)
+ Serial.print("\t");
+
+ Serial.print(digitalInputStatus[i].Value ? 100 : 0);
+ }
+
+ Serial.println();
+ }
+ #endif
}
+#ifndef DebugOutputPlotter
void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_payload, uint8_t port)
{
switch (min_id)
@@ -248,7 +353,12 @@ void processDigitalOutputMessage(uint8_t *min_payload, uint8_t len_payload)
byte outputIndex = min_payload[0];
if (outputIndex < DigitalOutputCount)
+ {
digitalWrite(DigitalOutputPin[min_payload[0]], min_payload[1] == 0 ? LOW : HIGH);
+
+ focusType = FocusTypeOutput;
+ focusOutputTime = millis();
+ }
else
outputError("Invalid digital output index: " + String(outputIndex));
}
@@ -258,6 +368,7 @@ void processQuitMessage()
{
active = false;
}
+#endif
byte getAnalogValue(byte analogInputIndex)
@@ -285,19 +396,25 @@ bool getDigitalValue(byte digitalInputIndex)
void outputAnalogValue(byte analogInputIndex, byte newValue)
{
+ #ifndef DebugOutputPlotter
byte payload[2] = { analogInputIndex, newValue };
min_send_frame(&minContext, FrameIDAnalogInput, (uint8_t *)payload, 2);
+ #endif
}
void outputDigitalValue(byte digitalInputIndex, bool newValue)
{
+ #ifndef DebugOutputPlotter
byte payload[2] = { digitalInputIndex, newValue ? 1 : 0 };
min_send_frame(&minContext, FrameIDDigitalInput, (uint8_t *)payload, 2);
+ #endif
}
void outputError(String message)
{
+ #ifndef DebugOutputPlotter
min_send_frame(&minContext, FrameIDError, (uint8_t *)message.c_str(), message.length());
+ #endif
}
diff --git a/README.md b/README.md
index 4bc27bc..aca60d5 100644
--- a/README.md
+++ b/README.md
@@ -22,4 +22,7 @@ Because of the second requirement, a simple media keys HID device does not suffi
## Developing
The hardware side uses an Arduino sketch to communicate the hardware state over the serial port.
-The Windows software is written in C# using .NET Framework 4.7.2 and Visual Studio 2019.
\ No newline at end of file
+The Windows software is written in C# using .NET Framework 4.7.2 and Visual Studio 2019.
+
+
+Some icons courtesy of https://feathericons.com/
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj
index ba6f0d3..1cab7bc 100644
--- a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj
+++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnob.Plugin.CoreAudio.csproj
@@ -17,7 +17,7 @@
true
full
false
- $(localappdata)\MassiveKnob\Plugins\
+ $(localappdata)\MassiveKnob\Plugins\CoreAudio\
DEBUG;TRACE
prompt
4
@@ -145,6 +145,10 @@
MSBuild:Compile
-
+
+
+ Always
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobPlugin.json
new file mode 100644
index 0000000..26c5892
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.CoreAudio/MassiveKnobPlugin.json
@@ -0,0 +1,3 @@
+{
+ "EntryAssembly": "MassiveKnob.Plugin.CoreAudio.dll"
+}
diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs
index 4c70f5e..9f1476d 100644
--- a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs
+++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs
@@ -8,8 +8,8 @@ namespace MassiveKnob.Plugin.EmulatorDevice.Devices
public class EmulatorDevice : IMassiveKnobDevice
{
public Guid DeviceId { get; } = new Guid("e1a4977a-abf4-4c75-a17d-fd8d3a8451ff");
- public string Name { get; } = "Mock device";
- public string Description { get; } = "Emulates the actual device but does not communicate with anything.";
+ public string Name { get; } = "Emulator";
+ public string Description { get; } = "Emulates an actual device but does not communicate with anything.";
public IMassiveKnobDeviceInstance Create(ILogger logger)
{
diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj
index c2e488d..92e4d38 100644
--- a/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj
+++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnob.Plugin.EmulatorDevice.csproj
@@ -17,7 +17,7 @@
true
full
false
- $(localappdata)\MassiveKnob\Plugins\
+ $(localappdata)\MassiveKnob\Plugins\EmulatorDevice\
DEBUG;TRACE
prompt
4
@@ -79,5 +79,10 @@
MSBuild:Compile
+
+
+ Always
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobPlugin.json
new file mode 100644
index 0000000..151b5ed
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/MassiveKnobPlugin.json
@@ -0,0 +1,3 @@
+{
+ "EntryAssembly": "MassiveKnob.Plugin.EmulatorDevice.dll"
+}
diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj
index ff6babc..17e0d9f 100644
--- a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj
+++ b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnob.Plugin.SerialDevice.csproj
@@ -17,7 +17,7 @@
true
full
false
- $(localappdata)\MassiveKnob\Plugins\
+ $(localappdata)\MassiveKnob\Plugins\SerialDevice\
DEBUG;TRACE
prompt
4
@@ -79,9 +79,17 @@
1.2.0
+
+ 0.11.24
+
5.0.0
+
+
+ Always
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobPlugin.json b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobPlugin.json
new file mode 100644
index 0000000..203ad71
--- /dev/null
+++ b/Windows/MassiveKnob.Plugin.SerialDevice/MassiveKnobPlugin.json
@@ -0,0 +1,3 @@
+{
+ "EntryAssembly": "MassiveKnob.Plugin.SerialDevice.dll"
+}
diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs
index 3e894f0..5381354 100644
--- a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs
+++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsView.xaml.cs
@@ -1,14 +1,22 @@
-namespace MassiveKnob.Plugin.SerialDevice.Settings
+using System;
+
+namespace MassiveKnob.Plugin.SerialDevice.Settings
{
///
/// Interaction logic for SerialDeviceSettingsView.xaml
///
- public partial class SerialDeviceSettingsView
+ public partial class SerialDeviceSettingsView : IDisposable
{
public SerialDeviceSettingsView(SerialDeviceSettingsViewModel viewModel)
{
DataContext = viewModel;
InitializeComponent();
}
+
+
+ public void Dispose()
+ {
+ (DataContext as SerialDeviceSettingsViewModel)?.Dispose();
+ }
}
}
diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs
index 54f0346..c2d0b3e 100644
--- a/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs
+++ b/Windows/MassiveKnob.Plugin.SerialDevice/Settings/SerialDeviceSettingsViewModel.cs
@@ -1,19 +1,23 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Runtime.CompilerServices;
+using Dapplo.Windows.Devices;
+using Dapplo.Windows.Devices.Enums;
namespace MassiveKnob.Plugin.SerialDevice.Settings
{
- public class SerialDeviceSettingsViewModel : INotifyPropertyChanged
+ public class SerialDeviceSettingsViewModel : IDisposable, INotifyPropertyChanged, IObserver
{
private readonly SerialDeviceSettings settings;
- private IEnumerable serialPorts;
+ private IList serialPorts;
+ private readonly IDisposable deviceSubscription;
public event PropertyChangedEventHandler PropertyChanged;
// ReSharper disable UnusedMember.Global - used by WPF Binding
- public IEnumerable SerialPorts
+ public IList SerialPorts
{
get => serialPorts;
set
@@ -29,7 +33,7 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
get => settings.PortName;
set
{
- if (value == settings.PortName)
+ if (value == settings.PortName || value == null)
return;
settings.PortName = value;
@@ -72,8 +76,13 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
this.settings = settings;
serialPorts = SerialPort.GetPortNames();
-
- // TODO (must have - port from old source) subscribe to device notification to refresh list
+ deviceSubscription = DeviceNotification.OnNotification.Subscribe(this);
+ }
+
+
+ public void Dispose()
+ {
+ deviceSubscription.Dispose();
}
@@ -87,5 +96,31 @@ namespace MassiveKnob.Plugin.SerialDevice.Settings
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
+
+
+ protected virtual void OnOtherPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+
+
+ public void OnNext(DeviceNotificationEvent value)
+ {
+ if ((value.EventType == DeviceChangeEvent.DeviceArrival ||
+ value.EventType == DeviceChangeEvent.DeviceRemoveComplete) &&
+ value.Is(DeviceBroadcastDeviceType.DeviceInterface))
+ {
+ SerialPorts = SerialPort.GetPortNames();
+ }
+ }
+
+ public void OnError(Exception error)
+ {
+ }
+
+ public void OnCompleted()
+ {
+ }
}
}
diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs
index 87a567d..1449597 100644
--- a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs
+++ b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs
@@ -49,6 +49,8 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
public void Connect(string portName, int baudRate, bool dtrEnable)
{
+ context.Connecting();
+
lock (minProtocolLock)
{
if (portName == lastPortName && baudRate == lastBaudRate && dtrEnable == lastDtrEnable)
@@ -75,14 +77,28 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
public void SetAnalogOutput(int analogOutputIndex, byte value)
{
- minProtocol?.QueueFrame(
+ IMINProtocol instance;
+
+ lock (minProtocolLock)
+ {
+ instance = minProtocol;
+ }
+
+ instance?.QueueFrame(
(byte)MassiveKnobFrameID.AnalogOutput,
new [] { (byte)analogOutputIndex, value });
}
public void SetDigitalOutput(int digitalOutputIndex, bool on)
{
- minProtocol?.QueueFrame(
+ IMINProtocol instance;
+
+ lock (minProtocolLock)
+ {
+ instance = minProtocol;
+ }
+
+ instance?.QueueFrame(
(byte)MassiveKnobFrameID.DigitalOutput,
new [] { (byte)digitalOutputIndex, on ? (byte)1 : (byte)0 });
}
@@ -90,16 +106,42 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
private void MinProtocolOnOnConnected(object sender, EventArgs e)
{
+ IMINProtocol instance;
+
+ lock (minProtocolLock)
+ {
+ if (minProtocol != sender as IMINProtocol)
+ return;
+
+ instance = minProtocol;
+ }
+
+ if (instance == null)
+ return;
+
Task.Run(async () =>
{
- await minProtocol.Reset();
- await minProtocol.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
+ await instance.Reset();
+ await instance.QueueFrame((byte)MassiveKnobFrameID.Handshake, new[] { (byte)'M', (byte)'K' });
});
}
private void MinProtocolOnOnFrame(object sender, MINFrameEventArgs e)
{
+ IMINProtocol instance;
+
+ lock (minProtocolLock)
+ {
+ if (minProtocol != sender as IMINProtocol)
+ return;
+
+ instance = minProtocol;
+ }
+
+ if (instance == null)
+ return;
+
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - by design
switch ((MassiveKnobFrameID)e.Id)
{
@@ -108,6 +150,8 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
{
logger.LogError("Invalid handshake response length, expected 4, got {length}: {payload}",
e.Payload.Length, BitConverter.ToString(e.Payload));
+
+ Disconnect();
return;
}
@@ -153,7 +197,10 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker
lock (minProtocolLock)
{
minProtocol?.Dispose();
+ minProtocol = null;
}
+
+ context.Disconnected();
}
diff --git a/Windows/MassiveKnob/App.xaml.cs b/Windows/MassiveKnob/App.xaml.cs
index 5192f72..b0b699b 100644
--- a/Windows/MassiveKnob/App.xaml.cs
+++ b/Windows/MassiveKnob/App.xaml.cs
@@ -51,7 +51,10 @@ namespace MassiveKnob
settingsWindow.Show();
}
else
+ {
+ settingsWindow.WindowState = WindowState.Normal;
settingsWindow.Activate();
+ }
}
diff --git a/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Core/IMassiveKnobOrchestrator.cs
similarity index 88%
rename from Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs
rename to Windows/MassiveKnob/Core/IMassiveKnobOrchestrator.cs
index e0737a3..a13b2d0 100644
--- a/Windows/MassiveKnob/Model/IMassiveKnobOrchestrator.cs
+++ b/Windows/MassiveKnob/Core/IMassiveKnobOrchestrator.cs
@@ -1,7 +1,8 @@
using System;
using MassiveKnob.Plugin;
+using MassiveKnob.Settings;
-namespace MassiveKnob.Model
+namespace MassiveKnob.Core
{
public interface IMassiveKnobOrchestrator : IDisposable
{
@@ -12,6 +13,9 @@ namespace MassiveKnob.Model
MassiveKnobActionInfo GetAction(MassiveKnobActionType actionType, int index);
MassiveKnobActionInfo SetAction(MassiveKnobActionType actionType, int index, IMassiveKnobAction action);
+
+ MassiveKnobSettings GetSettings();
+ void UpdateSettings(Action applyChanges);
}
diff --git a/Windows/MassiveKnob/Model/IPluginManager.cs b/Windows/MassiveKnob/Core/IPluginManager.cs
similarity index 89%
rename from Windows/MassiveKnob/Model/IPluginManager.cs
rename to Windows/MassiveKnob/Core/IPluginManager.cs
index 9f6a2c4..bbc8710 100644
--- a/Windows/MassiveKnob/Model/IPluginManager.cs
+++ b/Windows/MassiveKnob/Core/IPluginManager.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using MassiveKnob.Plugin;
-namespace MassiveKnob.Model
+namespace MassiveKnob.Core
{
public interface IPluginManager
{
diff --git a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Core/MassiveKnobOrchestrator.cs
similarity index 87%
rename from Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs
rename to Windows/MassiveKnob/Core/MassiveKnobOrchestrator.cs
index ac75a01..47e423f 100644
--- a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs
+++ b/Windows/MassiveKnob/Core/MassiveKnobOrchestrator.cs
@@ -9,7 +9,7 @@ using Newtonsoft.Json.Linq;
using Serilog.Extensions.Logging;
using ILogger = Serilog.ILogger;
-namespace MassiveKnob.Model
+namespace MassiveKnob.Core
{
public class MassiveKnobOrchestrator : IMassiveKnobOrchestrator
{
@@ -17,7 +17,7 @@ namespace MassiveKnob.Model
private readonly ILogger logger;
private readonly object settingsLock = new object();
- private Settings.Settings settings;
+ private MassiveKnobSettings massiveKnobSettings;
private readonly SerialQueue flushSettingsQueue = new SerialQueue();
private MassiveKnobDeviceInfo activeDevice;
@@ -49,15 +49,17 @@ namespace MassiveKnob.Model
public IObservable ActiveDeviceSubject => activeDeviceInfoSubject;
- public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger)
+ public MassiveKnobOrchestrator(IPluginManager pluginManager, ILogger logger, MassiveKnobSettings massiveKnobSettings)
{
this.pluginManager = pluginManager;
this.logger = logger;
+ this.massiveKnobSettings = massiveKnobSettings;
}
public void Dispose()
{
+ activeDeviceContext = null;
activeDevice?.Instance?.Dispose();
void DisposeMappings(ICollection mappings)
@@ -85,13 +87,11 @@ namespace MassiveKnob.Model
{
lock (settingsLock)
{
- settings = SettingsJsonSerializer.Deserialize();
-
- if (settings.Device == null)
+ if (massiveKnobSettings.Device == null)
return;
var allDevices = pluginManager.GetDevicePlugins().SelectMany(dp => dp.Devices);
- var device = allDevices.FirstOrDefault(d => d.DeviceId == settings.Device.DeviceId);
+ var device = allDevices.FirstOrDefault(d => d.DeviceId == massiveKnobSettings.Device.DeviceId);
InternalSetActiveDevice(device, false);
}
@@ -135,7 +135,7 @@ namespace MassiveKnob.Model
while (index >= settingsList.Count)
settingsList.Add(null);
- settingsList[index] = action == null ? null : new Settings.Settings.ActionSettings
+ settingsList[index] = action == null ? null : new MassiveKnobSettings.ActionSettings
{
ActionId = action.ActionId,
Settings = null
@@ -161,6 +161,26 @@ namespace MassiveKnob.Model
return mapping?.ActionInfo;
}
+
+ public MassiveKnobSettings GetSettings()
+ {
+ lock (settingsLock)
+ {
+ return massiveKnobSettings.Clone();
+ }
+ }
+
+
+ public void UpdateSettings(Action applyChanges)
+ {
+ lock (settingsLock)
+ {
+ applyChanges(massiveKnobSettings);
+ }
+
+ FlushSettings();
+ }
+
private MassiveKnobDeviceInfo InternalSetActiveDevice(IMassiveKnobDevice device, bool resetSettings)
{
@@ -173,10 +193,10 @@ namespace MassiveKnob.Model
lock (settingsLock)
{
if (device == null)
- settings.Device = null;
+ massiveKnobSettings.Device = null;
else
{
- settings.Device = new Settings.Settings.DeviceSettings
+ massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
{
DeviceId = device.DeviceId,
Settings = null
@@ -191,7 +211,7 @@ namespace MassiveKnob.Model
if (device != null)
{
- var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Device", device.DeviceId)).CreateLogger(null));
+ var instance = device.Create(new SerilogLoggerProvider(logger.ForContext("Context", new { Device = device.DeviceId })).CreateLogger(null));
ActiveDevice = new MassiveKnobDeviceInfo(device, instance, null);
activeDeviceContext = new DeviceContext(this, device);
@@ -210,11 +230,11 @@ namespace MassiveKnob.Model
protected T GetDeviceSettings(IMassiveKnobDeviceContext context) where T : class, new()
{
if (context != activeDeviceContext)
- throw new InvalidOperationException("Caller must be the active device to retrieve the settings");
+ throw new InvalidOperationException("Caller must be the active device to retrieve the massiveKnobSettings");
lock (settingsLock)
{
- return settings.Device.Settings?.ToObject() ?? new T();
+ return massiveKnobSettings.Device.Settings?.ToObject() ?? new T();
}
}
@@ -222,17 +242,17 @@ namespace MassiveKnob.Model
protected void SetDeviceSettings(IMassiveKnobDeviceContext context, IMassiveKnobDevice device, T deviceSettings) where T : class, new()
{
if (context != activeDeviceContext)
- throw new InvalidOperationException("Caller must be the active device to update the settings");
+ throw new InvalidOperationException("Caller must be the active device to update the massiveKnobSettings");
lock (settingsLock)
{
- if (settings.Device == null)
- settings.Device = new Settings.Settings.DeviceSettings
+ if (massiveKnobSettings.Device == null)
+ massiveKnobSettings.Device = new MassiveKnobSettings.DeviceSettings
{
DeviceId = device.DeviceId
};
- settings.Device.Settings = JObject.FromObject(deviceSettings);
+ massiveKnobSettings.Device.Settings = JObject.FromObject(deviceSettings);
}
FlushSettings();
@@ -248,7 +268,7 @@ namespace MassiveKnob.Model
return new T();
if (list[index]?.Context != context)
- throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
+ throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
var settingsList = GetActionSettingsList(action.ActionType);
if (index >= settingsList.Count)
@@ -268,7 +288,7 @@ namespace MassiveKnob.Model
return;
if (list[index]?.Context != context)
- throw new InvalidOperationException("Caller must be the active action to retrieve the settings");
+ throw new InvalidOperationException("Caller must be the active action to retrieve the massiveKnobSettings");
var settingsList = GetActionSettingsList(action.ActionType);
@@ -276,7 +296,7 @@ namespace MassiveKnob.Model
settingsList.Add(null);
if (settingsList[index] == null)
- settingsList[index] = new Settings.Settings.ActionSettings
+ settingsList[index] = new MassiveKnobSettings.ActionSettings
{
ActionId = action.ActionId
};
@@ -408,21 +428,21 @@ namespace MassiveKnob.Model
}
- private List GetActionSettingsList(MassiveKnobActionType actionType)
+ private List GetActionSettingsList(MassiveKnobActionType actionType)
{
switch (actionType)
{
case MassiveKnobActionType.InputAnalog:
- return settings.AnalogInput;
+ return massiveKnobSettings.AnalogInput;
case MassiveKnobActionType.InputDigital:
- return settings.DigitalInput;
+ return massiveKnobSettings.DigitalInput;
case MassiveKnobActionType.OutputAnalog:
- return settings.AnalogOutput;
+ return massiveKnobSettings.AnalogOutput;
case MassiveKnobActionType.OutputDigital:
- return settings.DigitalOutput;
+ return massiveKnobSettings.DigitalOutput;
default:
throw new ArgumentOutOfRangeException(nameof(actionType), actionType, null);
@@ -431,16 +451,16 @@ namespace MassiveKnob.Model
private void FlushSettings()
{
- Settings.Settings settingsSnapshot;
+ MassiveKnobSettings massiveKnobSettingsSnapshot;
lock (settingsLock)
{
- settingsSnapshot = settings.Clone();
+ massiveKnobSettingsSnapshot = massiveKnobSettings.Clone();
}
flushSettingsQueue.Enqueue(async () =>
{
- await SettingsJsonSerializer.Serialize(settingsSnapshot);
+ await MassiveKnobSettingsJsonSerializer.Serialize(massiveKnobSettingsSnapshot);
});
}
@@ -462,10 +482,10 @@ namespace MassiveKnob.Model
lock (settingsLock)
{
- UpdateMapping(analogInputs, specs.AnalogInputCount, settings.AnalogInput, DelayedInitialize);
- UpdateMapping(digitalInputs, specs.DigitalInputCount, settings.DigitalInput, DelayedInitialize);
- UpdateMapping(analogOutputs, specs.AnalogOutputCount, settings.AnalogOutput, DelayedInitialize);
- UpdateMapping(digitalOutputs, specs.DigitalOutputCount, settings.DigitalOutput, DelayedInitialize);
+ UpdateMapping(analogInputs, specs.AnalogInputCount, massiveKnobSettings.AnalogInput, DelayedInitialize);
+ UpdateMapping(digitalInputs, specs.DigitalInputCount, massiveKnobSettings.DigitalInput, DelayedInitialize);
+ UpdateMapping(analogOutputs, specs.AnalogOutputCount, massiveKnobSettings.AnalogOutput, DelayedInitialize);
+ UpdateMapping(digitalOutputs, specs.DigitalOutputCount, massiveKnobSettings.DigitalOutput, DelayedInitialize);
}
foreach (var delayedInitializeAction in delayedInitializeActions)
@@ -487,7 +507,7 @@ namespace MassiveKnob.Model
}
- private void UpdateMapping(List mapping, int newCount, List actionSettings, Action initializeOutsideLock)
+ private void UpdateMapping(List mapping, int newCount, List actionSettings, Action initializeOutsideLock)
{
if (mapping.Count > newCount)
{
@@ -524,10 +544,13 @@ namespace MassiveKnob.Model
if (action == null)
return null;
- var actionLogger = logger
- .ForContext("Action", action.ActionId)
- .ForContext("ActionType", action.ActionType)
- .ForContext("Index", index);
+ var actionLogger = logger.ForContext("Context",
+ new
+ {
+ Action = action.ActionId,
+ action.ActionType,
+ Index = index
+ });
var instance = action.Create(new SerilogLoggerProvider(actionLogger).CreateLogger(null));
var context = new ActionContext(this, action, index);
diff --git a/Windows/MassiveKnob/Model/PluginManager.cs b/Windows/MassiveKnob/Core/PluginManager.cs
similarity index 70%
rename from Windows/MassiveKnob/Model/PluginManager.cs
rename to Windows/MassiveKnob/Core/PluginManager.cs
index a41505c..198a3eb 100644
--- a/Windows/MassiveKnob/Model/PluginManager.cs
+++ b/Windows/MassiveKnob/Core/PluginManager.cs
@@ -3,11 +3,13 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Text;
using MassiveKnob.Plugin;
+using Newtonsoft.Json;
using Serilog;
using Serilog.Extensions.Logging;
-namespace MassiveKnob.Model
+namespace MassiveKnob.Core
{
public class MassiveKnobPluginIdConflictException : Exception
{
@@ -75,26 +77,70 @@ namespace MassiveKnob.Model
private void LoadPlugins(string path, RegisteredIds registeredIds, Action onException)
{
+ logger.Information("Checking {path} for plugins...", path);
if (!Directory.Exists(path))
return;
- var filenames = Directory.GetFiles(path, "*.dll");
-
- foreach (var filename in filenames)
+
+ var metadataFilenames = Directory.GetFiles(path, "MassiveKnobPlugin.json", SearchOption.AllDirectories);
+
+ foreach (var metadataFilename in metadataFilenames)
{
+ var pluginPath = Path.GetDirectoryName(metadataFilename);
+ if (string.IsNullOrEmpty(pluginPath))
+ continue;
+
+ PluginMetadata pluginMetadata;
try
{
- var pluginAssembly = Assembly.LoadFrom(filename);
- RegisterPlugins(filename, pluginAssembly, registeredIds);
+ pluginMetadata = LoadMetadata(metadataFilename);
}
catch (Exception e)
{
- onException(e, filename);
+ logger.Warning("Could not load plugin metadata from {metadataFilename}: {message}", metadataFilename, e.Message);
+ continue;
+ }
+
+ var entryAssemblyFilename = Path.Combine(pluginPath, pluginMetadata.EntryAssembly);
+ if (!File.Exists(entryAssemblyFilename))
+ {
+ logger.Warning("Entry assembly specified in {metadataFilename} does not exist: {entryAssemblyFilename}", entryAssemblyFilename);
+ continue;
+ }
+
+ try
+ {
+ logger.Information("Plugin found in {pluginPath}", pluginPath);
+
+ var pluginAssembly = Assembly.LoadFrom(entryAssemblyFilename);
+ RegisterPlugins(entryAssemblyFilename, pluginAssembly, registeredIds);
+ }
+ catch (Exception e)
+ {
+ logger.Warning("Error while loading plugin {entryAssemblyFilename}: {message}", entryAssemblyFilename, e.Message);
+ onException(e, entryAssemblyFilename);
}
}
}
+ private static PluginMetadata LoadMetadata(string filename)
+ {
+ string json;
+
+ using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, true))
+ using (var streamReader = new StreamReader(stream, Encoding.UTF8))
+ {
+ json = streamReader.ReadToEnd();
+ }
+
+ if (string.IsNullOrEmpty(json))
+ throw new IOException("Metadata file is empty");
+
+ return JsonConvert.DeserializeObject(json);
+ }
+
+
private void RegisterPlugins(string filename, Assembly assembly, RegisteredIds registeredIds)
{
var pluginTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute() != null);
@@ -104,7 +150,10 @@ namespace MassiveKnob.Model
if (!(pluginInstance is IMassiveKnobPlugin))
throw new InvalidCastException($"Type {pluginType.FullName} claims to be a MassiveKnobPlugin but does not implement IMassiveKnobPlugin");
- ValidateRegistration(filename, (IMassiveKnobPlugin)pluginInstance, registeredIds);
+ var plugin = (IMassiveKnobPlugin) pluginInstance;
+ logger.Information("Found plugin with Id {pluginId}: {name}", plugin.PluginId, plugin.Name);
+
+ ValidateRegistration(filename, plugin, registeredIds);
plugins.Add((IMassiveKnobPlugin)pluginInstance);
}
}
@@ -125,6 +174,8 @@ namespace MassiveKnob.Model
{
foreach (var device in devicePlugin.Devices)
{
+ logger.Information("- Device {deviceId}: {name}", device.DeviceId, device.Name);
+
if (registeredIds.DeviceById.TryGetValue(device.DeviceId, out var conflictingDeviceFilename))
throw new MassiveKnobPluginIdConflictException(device.DeviceId, conflictingDeviceFilename, filename);
@@ -138,6 +189,8 @@ namespace MassiveKnob.Model
{
foreach (var action in actionPlugin.Actions)
{
+ logger.Information("- Action {actionId}: {name}", action.ActionId, action.Name);
+
if (registeredIds.ActionById.TryGetValue(action.ActionId, out var conflictingActionFilename))
throw new MassiveKnobPluginIdConflictException(action.ActionId, conflictingActionFilename, filename);
@@ -184,5 +237,11 @@ namespace MassiveKnob.Model
public readonly Dictionary DeviceById = new Dictionary();
public readonly Dictionary ActionById = new Dictionary();
}
+
+
+ private class PluginMetadata
+ {
+ public string EntryAssembly { get; set; }
+ }
}
}
diff --git a/Windows/MassiveKnob/Helpers/ComparisonConverter.cs b/Windows/MassiveKnob/Helpers/ComparisonConverter.cs
new file mode 100644
index 0000000..a012b3f
--- /dev/null
+++ b/Windows/MassiveKnob/Helpers/ComparisonConverter.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Windows.Data;
+
+namespace MassiveKnob.Helpers
+{
+ public class ComparisonConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value?.Equals(parameter);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+ {
+ return value?.Equals(true) == true ? parameter : Binding.DoNothing;
+ }
+ }
+}
diff --git a/Windows/MassiveKnob/Helpers/DelegateCommand.cs b/Windows/MassiveKnob/Helpers/DelegateCommand.cs
deleted file mode 100644
index 8266c22..0000000
--- a/Windows/MassiveKnob/Helpers/DelegateCommand.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-using System;
-using System.Windows.Input;
-
-namespace MassiveKnob.Helpers
-{
- public class DelegateCommand : ICommand
- {
- private readonly Action execute;
- private readonly Func canExecute;
-
-
- public DelegateCommand(Action execute) : this(execute, null)
- {
- }
-
-
- public DelegateCommand(Action execute, Func canExecute)
- {
- this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
- this.canExecute = canExecute;
- }
-
-
- public bool CanExecute(object parameter)
- {
- return canExecute?.Invoke() ?? true;
- }
-
-
- public void Execute(object parameter)
- {
- execute();
- }
-
-
- public event EventHandler CanExecuteChanged
- {
- add => CommandManager.RequerySuggested += value;
- remove => CommandManager.RequerySuggested -= value;
- }
- }
-
-
- public class DelegateCommand : ICommand
- {
- private readonly Action execute;
- private readonly Predicate canExecute;
-
-
- public DelegateCommand(Action execute) : this(execute, null)
- {
- }
-
-
- public DelegateCommand(Action execute, Predicate canExecute)
- {
- this.execute = execute ?? throw new ArgumentNullException(nameof(execute));
- this.canExecute = canExecute;
- }
-
-
- public bool CanExecute(object parameter)
- {
- return canExecute?.Invoke((T)parameter) ?? true;
- }
-
-
- public void Execute(object parameter)
- {
- execute((T)parameter);
- }
-
-
- public event EventHandler CanExecuteChanged
- {
- add => CommandManager.RequerySuggested += value;
- remove => CommandManager.RequerySuggested -= value;
- }
- }
-}
-*/
\ No newline at end of file
diff --git a/Windows/MassiveKnob/MainIcon.ico b/Windows/MassiveKnob/MainIcon.ico
deleted file mode 100644
index b4abcb4..0000000
Binary files a/Windows/MassiveKnob/MainIcon.ico and /dev/null differ
diff --git a/Windows/MassiveKnob/MassiveKnob.csproj b/Windows/MassiveKnob/MassiveKnob.csproj
index 04c9df8..5756b27 100644
--- a/Windows/MassiveKnob/MassiveKnob.csproj
+++ b/Windows/MassiveKnob/MassiveKnob.csproj
@@ -36,7 +36,7 @@
false
- MainIcon.ico
+ Resources\MainIcon.ico
MassiveKnob.Program
@@ -59,15 +59,19 @@
-
+
-
-
-
-
+
+
+
+
+
+
+
+
InputOutputView.xaml
@@ -77,16 +81,38 @@
-
-
+
+
True
True
Strings.resx
+
+ AnalogInputsView.xaml
+
+
+ AnalogOutputsView.xaml
+
+
+ StartupView.xaml
+
+
+ LoggingView.xaml
+
+
+ DigitalInputsView.xaml
+
+
+ DigitalOutputsView.xaml
+
+
+ DeviceView.xaml
+
- ResXFileCodeGenerator
+ PublicResXFileCodeGenerator
Strings.Designer.cs
+ Designer
@@ -134,7 +160,7 @@
-
+
@@ -146,6 +172,58 @@
App.xaml
Code
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs
index 352b698..e3d0a56 100644
--- a/Windows/MassiveKnob/Program.cs
+++ b/Windows/MassiveKnob/Program.cs
@@ -2,39 +2,36 @@
using System.IO;
using System.Text;
using System.Windows;
-using MassiveKnob.Model;
+using MassiveKnob.Core;
+using MassiveKnob.Settings;
using MassiveKnob.View;
using MassiveKnob.ViewModel;
using Serilog;
-using Serilog.Core;
-using Serilog.Events;
using SimpleInjector;
-
namespace MassiveKnob
{
public static class Program
{
- ///
- /// The main entry point for the application.
- ///
[STAThread]
public static int Main()
{
- // TODO (should have) make configurable
- var loggingLevelSwitch = new LoggingLevelSwitch();
- //var loggingLevelSwitch = new LoggingLevelSwitch(LogEventLevel.Verbose);
+ var settings = MassiveKnobSettingsJsonSerializer.Deserialize();
+
+ var loggingSwitch = new LoggingSwitch();
+ loggingSwitch.SetLogging(settings.Log.Enabled, settings.Log.Level);
var logger = new LoggerConfiguration()
- //.MinimumLevel.Verbose()
- .MinimumLevel.ControlledBy(loggingLevelSwitch)
+ .Filter.ByIncludingOnly(loggingSwitch.IsIncluded)
.Enrich.FromLogContext()
.WriteTo.File(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"MassiveKnob", @"Logs", @".log"),
- LogEventLevel.Verbose, rollingInterval: RollingInterval.Day)
+ rollingInterval: RollingInterval.Day,
+ outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{@Context}{NewLine}{Exception}")
.CreateLogger();
+ logger.Information("MassiveKnob starting");
var pluginManager = new PluginManager(logger);
var messages = new StringBuilder();
@@ -49,7 +46,7 @@ namespace MassiveKnob
return 1;
}
- var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger);
+ var orchestrator = new MassiveKnobOrchestrator(pluginManager, logger, settings);
orchestrator.Load();
@@ -57,7 +54,7 @@ namespace MassiveKnob
container.Options.EnableAutoVerification = false;
container.RegisterInstance(logger);
- container.RegisterInstance(loggingLevelSwitch);
+ container.RegisterInstance(loggingSwitch);
container.RegisterInstance(pluginManager);
container.RegisterInstance(orchestrator);
@@ -69,6 +66,7 @@ namespace MassiveKnob
var app = container.GetInstance();
app.Run();
+ logger.Information("MassiveKnob shutting down");
orchestrator.Dispose();
return 0;
}
diff --git a/Windows/MassiveKnob/Resources/Analog.xaml b/Windows/MassiveKnob/Resources/Analog.xaml
new file mode 100644
index 0000000..d678219
--- /dev/null
+++ b/Windows/MassiveKnob/Resources/Analog.xaml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob/Resources/Device.xaml b/Windows/MassiveKnob/Resources/Device.xaml
new file mode 100644
index 0000000..1b4f5ed
--- /dev/null
+++ b/Windows/MassiveKnob/Resources/Device.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob/Resources/Digital.xaml b/Windows/MassiveKnob/Resources/Digital.xaml
new file mode 100644
index 0000000..09faf4b
--- /dev/null
+++ b/Windows/MassiveKnob/Resources/Digital.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Windows/MassiveKnob/Resources/Icon.ai b/Windows/MassiveKnob/Resources/Icon.ai
index 12f18b3..0192c4a 100644
--- a/Windows/MassiveKnob/Resources/Icon.ai
+++ b/Windows/MassiveKnob/Resources/Icon.ai
@@ -1,5 +1,5 @@
%PDF-1.5
%
-1 0 obj
<>/OCGs[5 0 R 6 0 R 35 0 R 36 0 R 37 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+1 0 obj
<>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
@@ -8,24 +8,24 @@
application/pdf
- Print
+ Icon
- 2021-02-19T10:48:23+01:00
- 2021-02-19T10:48:23+01:00
- 2021-02-19T10:44:53+01:00
+ 2021-03-05T08:59:26+01:00
+ 2021-03-05T08:59:26+01:00
+ 2021-03-05T08:59:26+02:00
Adobe Illustrator CS6 (Windows)
256
- 216
+ 256
JPEG
- /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA2AEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7
FXYq7FWN+ZvzH8keWeS6xq8EE69bVCZZ+ld4ow7iviRTFXl+vf8AOVGiwlo9C0ee8NKLPdusCV8Q
ieqzD5lcVYVP/wA5Afm5rtwIdFhit5CaLDYWhuHP0S+vU/IYqsj0n/nJLXqgtrKK4oyyzmxUgg1B
V3gH0UxVw/If85b48rx1VjU1uL3ma9P2TJvilXX/AJxe/MORRI99pau27K81wWBPiRAwr9OKFkn/
ADjV+Zts3G3ubGRSORaG4lUV8PjjjNcVWJ+Vf/OQGk8pLB7xaAljaakqEilTt6yE9OlMVa/xT/zk
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
irsVdirsVdirsVdirsVdirsVdirsVdir/9k=
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq
7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7
FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F
XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY
q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q==
@@ -34,8 +34,8 @@
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#">
- uuid:9cd59b36-41ae-48c3-8c27-fdad4bec3d28
- xmp.did:0F14401E9772EB11ADFCFBA4D8CDAE46
+ uuid:63912d2b-8456-4691-af03-78dc07e5fd0d
+ xmp.did:163E26B4887DEB11B1CFFB6D61C748E2
uuid:5D20892493BFDB11914A8590D31508C8
proof:pdf
@@ -48,8 +48,8 @@
saved
- xmp.iid:0F14401E9772EB11ADFCFBA4D8CDAE46
- 2021-02-19T10:44:53+01:00
+ xmp.iid:163E26B4887DEB11B1CFFB6D61C748E2
+ 2021-03-05T08:59:25+01:00
Adobe Illustrator CS6 (Windows)
/
@@ -69,18 +69,10 @@
False
1
- 256.000000
- 256.000000
+ 16.000000
+ 16.000000
Pixels
-
-
- Cyan
- Magenta
- Yellow
- Black
-
-
@@ -651,13 +643,11 @@
-endstream
endobj
3 0 obj
<>
endobj
8 0 obj
<>/Resources<>/ExtGState<>/Properties<>>>/Thumb 43 0 R/TrimBox[0.0 0.0 256.0 256.0]/Type/Page>>
endobj
39 0 obj
<>stream
-HUˎ1+퉓8+
ⴠ9T^+r1Mܕ.;q9q>t[-y9Kە,( u{2|Kܛww5B'=98 ~[-3IBz%y,8ھ6nY-uJKN,1{,╽
ER`bbwS#Ѷpl~ޑh $${pll/E>[%P`XPbչUNm7ˁ\f(/|;c/i7#9zv"p9 4M6еy"\^zRpQQhi
ۀUT" R<+¡90TmsBQFR`qֲS71< M.`@n,9#x.1'b@2jy,OR>[K)jQX8+j̈́)}ܱs~np/־n/m_r1荤}O
-endstream
endobj
43 0 obj
<>stream
-8;X^:gC4<0#Xf%]rQmUmCc3o=4T)dW`'bKYR8MMl"UOQ85alPcY%OgHmj5g&m9W&0
-bO+%0A/cn'K`p2[jf;!a_h3Z6@[73?5h
-endstream
endobj
44 0 obj
[/Indexed/DeviceRGB 255 45 0 R]
endobj
45 0 obj
<>stream
+endstream
endobj
3 0 obj
<>
endobj
7 0 obj
<>/Resources<>/ExtGState<>/Properties<>>>/Thumb 12 0 R/TrimBox[0.0 0.0 16.0 16.0]/Type/Page>>
endobj
8 0 obj
<>stream
+HTP WXZ(Ĥ?1zP(6,,Dq.tƓT4 ]p$>7M2exJuSC鹁pYFl[lfP'a9'ƢVŖsA"7'{`~&bUU׃"=w] J=
+endstream
endobj
12 0 obj
<>stream
+8;Xp,rVCYe!!<3,C'.`~>
+endstream
endobj
13 0 obj
[/Indexed/DeviceRGB 255 14 0 R]
endobj
14 0 obj
<>stream
8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0
b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup`
E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn
@@ -665,7 +655,7 @@ E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn
VNWFKf>nDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O(
l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~>
-endstream
endobj
35 0 obj
<>
endobj
36 0 obj
<>
endobj
37 0 obj
<>
endobj
50 0 obj
[/View/Design]
endobj
51 0 obj
<>>>
endobj
48 0 obj
[/View/Design]
endobj
49 0 obj
<>>>
endobj
46 0 obj
[/View/Design]
endobj
47 0 obj
<>>>
endobj
42 0 obj
<>
endobj
41 0 obj
[/ICCBased 52 0 R]
endobj
52 0 obj
<>stream
+endstream
endobj
5 0 obj
<>
endobj
15 0 obj
[/View/Design]
endobj
16 0 obj
<>>>
endobj
11 0 obj
<>
endobj
10 0 obj
[/ICCBased 17 0 R]
endobj
17 0 obj
<>stream
HuTKtKKJI,t(4K%ҹH4J#Ғ(H
wqyy~3̙g<3 Y9El
@ ]!O-@ \+BVKK
:OX~WCaiHKL 0qY `5 ck
@@ -2106,45 +2096,45 @@ p`+gQ
ݡP
`(dBG $8&{j?Sp䴥W5zNK6Vi|QAh4ݪ(d*isZdD(k}P` ͏Gg墂t5RLTc+ʻ#! 1Me+ƅxoBj0ǻ8OUN☤"ţ>|_JvV{J,͓ɯ)l/`
R|Vxfm96pL1c3Y0ߜ,/NP[@Qt+eKTe9ۏ-p
Ȯ|BpW$
%IHOy:~0?_(gD,rE}KcШ+)J_*=I,?!4l=Å[Pծ=Ğ [ }gOZO$o!xL=5dbBC) Oմ>RIr\r"#;@V2[kclzi5a#*Xm?;62.#:ĉ֙Li_ 8L+
-endstream
endobj
40 0 obj
<>
endobj
53 0 obj
<>
endobj
54 0 obj
<>stream
+endstream
endobj
9 0 obj
<>
endobj
18 0 obj
<>
endobj
19 0 obj
<>stream
%!PS-Adobe-3.0
%%Creator: Adobe Illustrator(R) 16.0
%%AI8_CreatorVersion: 16.0.0
%%For: (PsychoMark) ()
-%%Title: (Icon.ai)
-%%CreationDate: 2/19/2021 10:48 AM
+%%Title: (Untitled-1)
+%%CreationDate: 3/5/2021 8:59 AM
%%Canvassize: 16383
-%%BoundingBox: 2 -230 257 -18
-%%HiResBoundingBox: 2.9165 -229.4648 256.168 -18.2017
-%%DocumentProcessColors: Cyan Magenta Yellow Black
+%%BoundingBox: 0 -16 16 0
+%%HiResBoundingBox: 0.776855 -15.2227 15.2227 -0.776855
+%%DocumentProcessColors:
%AI5_FileFormat 12.0
%AI12_BuildNumber: 682
%AI3_ColorUsage: Color
%AI7_ImageSettings: 0
%%CMYKProcessColor: 1 1 1 1 ([Registration])
-%AI3_Cropmarks: 0 -256 256 0
-%AI3_TemplateBox: 128.5 -128.5 128.5 -128.5
-%AI3_TileBox: -178 -524 434 268
+%AI3_Cropmarks: 0 -16 16 0
+%AI3_TemplateBox: 8.5 -8.5 8.5 -8.5
+%AI3_TileBox: -298 -404 314 388
%AI3_DocumentPreview: None
%AI5_ArtSize: 14400 14400
%AI5_RulerUnits: 6
%AI9_ColorModel: 2
%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0
%AI5_TargetResolution: 800
-%AI5_NumLayers: 3
-%AI9_OpenToView: -427 249 1 1387 914 18 1 0 82 117 0 0 0 1 1 0 1 1 0 1
-%AI5_OpenViewLayers: 762
-%%PageOrigin:-178 -524
+%AI5_NumLayers: 1
+%AI9_OpenToView: -679 451 1 1389 914 18 0 0 82 117 0 0 0 1 1 0 1 1 0 1
+%AI5_OpenViewLayers: 7
+%%PageOrigin:-298 -404
%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9
%AI9_Flatten: 1
%AI12_CMSettings: 00.MS
%%EndComments
-endstream
endobj
55 0 obj
<>stream
-%%BoundingBox: 2 -230 257 -18
-%%HiResBoundingBox: 2.9165 -229.4648 256.168 -18.2017
-%AI7_Thumbnail: 128 108 8
-%%BeginData: 8681 Hex Bytes
+endstream
endobj
20 0 obj
<>stream
+%%BoundingBox: 0 -16 16 0
+%%HiResBoundingBox: 0.776855 -15.2227 15.2227 -0.776855
+%AI7_Thumbnail: 128 128 8
+%%BeginData: 2039 Hex Bytes
%0000330000660000990000CC0033000033330033660033990033CC0033FF
%0066000066330066660066990066CC0066FF009900009933009966009999
%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66
@@ -2171,2538 +2161,2401 @@ endstream
endobj
55 0 obj
<>stream
%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB
%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF
%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF
-%524C45FD34FFA87D7D5276525227522727275227522752527D7DA8A8FD65
-%FFA87D7DFD0627F82727270527272705272727F8FD062752527D7DFD5CFF
-%A8A85252FD07275227525276527D527D767D527D52522752FD09275276A8
-%FD55FFA87D4B272727F8FD04277676A7A8FD0FFFA8FF7D7D525227270527
-%0527275252A8FD50FFA85252FD0527527DA8A8FD1BFFA8A85252FD052752
-%7DFD4CFF7D52FD0527527DFD24FFA852522727F82727527DFD47FF7DFD05
-%27527DFD2BFF7D52FD042752A8FD43FF7D522727F82752A8FD2FFFA85227
-%272705527DFD3FFFA852FD04277DCAFD33FFA87DFD042752A8FD3BFFA876
-%2727055152FD39FF52FD042776A8FD38FF7D522727277DA8FD3BFFA87D27
-%2727527DFD36FF52FD0427A8FD16FFFD04A8A2A8A8FFA8FD20FFA84B27F8
-%2752A8FD32FFA8272727527DFD11FFA8A8767D5252275227272752275227
-%52527676A8A8FD1DFF7D52272727A8FD30FF7D27270552A8FD0EFFA87D52
-%FD0427F8272727F8FD0727F8FD06275276A8FD1AFFA8522727277DFD2EFF
-%7D2727277DFD0DFFA85252FD072752527D527D7DA1FD047DFD0452FD0727
-%527DA8FD19FFA127272752A8FD2AFFA852272727A8FD0CFF7D5227270527
-%2752527D7DFD11FF7D7D52522727F82727527DFD18FFA82727F8277DFD28
-%FFA852272727FD0BFFA87DFD05275276A8FD04FFA8A87D7DFD04524BFD04
-%527D7DA8A8FD04FFA85252FD05277DA8FD17FF522727277DFD26FFA85205
-%274BFD0BFF52270527274B52A8FFFFFFA85252FD0427F8272727F8272727
-%F8FD06275252A8FFFFFFA852FD052752FD17FF522727277DFD24FFA85227
-%2752FD0AFF7DFD0527A8FFFFFFA85252FD07275227525276527D52522752
-%FD07275252A8FFFFFF7DFD05277DFD16FF7D2727277DFD22FFA852F82752
-%FD09FF7D52272705527DFFFFA85227F82705272752527D7DFD0BFFA8A87D
-%7D52522727F827272752A8FFFF7D522727F852A8FD15FF7D27272752FD21
-%FF52272752FD09FF52FD0427A8FFFF7D52FD0527767DFD16FFA87D52FD05
-%2752A8FFFF7DFD04277CFD15FFA82727277DFD1FFF52272752FD08FFA827
-%27052752FFFFA84B27052727527DFD1DFF7D52FD042752A8FFFF52FD0427
-%FD15FF7D2727277DFD1DFF76272752FD08FF7D27272752A8FFA852FD0427
-%52A8FD21FFA8522D2727277DFFFF7D52272727A8FD14FFA8272727A1FD1B
-%FF7D272727FD07FFA8522727277DFFFF52FD042752A8FD25FFA8522727F8
-%527DFFFF522727277DFD14FF7D27F827A8FFA8FD17FFA8272727FD07FFA8
-%52272727A8FFFF5227272752A8FD29FFA84B27272752FFFF7D2727277DFD
-%14FF7D272752FD19FF272727A8FD06FFA127272752FFFFA8272705527DFD
-%2DFF76FD0427A8FFA827272752FD14FF52272752FD17FF5227277DFD06FF
-%7D52272752FFFF7D27272752A8FD2FFFA852272727A8FFA827272752FD14
-%FF5227277DFD15FF7D272752FD06FF7D27F82752FFFF522727277DFD33FF
-%7D2727277DFFFF27272752FD14FF272727A8FD13FFA8522752A8FD05FFA8
-%2727277DFFFF76272727A8FD35FFA12727277DFFFF2727277DFD13FFA827
-%2727FD13FF5205277DFD05FFA852272752FFFF52272727A8FD37FFA82727
-%2776FFFF2727277DFD13FF7D272752FD11FFA8272752FD06FF5227277DFF
-%FF52272727FD37FF7DA8FFFF2727277DFFA8272727A8FD13FF522727A8FD
-%10FF272727A8FD05FF52272752FFFF52F82727FD36FFA82727F852FFA827
-%27277DFFA8272727FD13FF7D272752FD0FFF7D27277DFD05FF7D272752FF
-%FF7D272727FD37FF272727522752FFFF272727A8FF7D272752FD13FF5227
-%27A8FD0EFF270552FD05FFA8272727FFFFA8272727FD36FFA8FD072752FF
-%A8272727A8FF5227277DFD13FF272727FD0DFF7D27277DFD05FF272727A8
-%FFFF272727A8FD35FFCA27272752FD05277DFFA1272727FFFF522752A8FD
-%12FF7D27277DFD0CFF272727FD05FF5227277DFFFF4B27F87DFD35FFA8FD
-%0A2752FFFF7D272752FF7D27F852FD12FFA8522727A8FD0AFF7D27277DFD
-%04FF7D272752FFFF7D272776FD35FFA8FD052752FD0527A8FFFFFF522727
-%A8FF522727A8FD12FF7D27277DFD0AFF520552FD05FF522727A8FFA82727
-%27A8FD34FFA8FD0B27A8FD04FFA8272752FFA8272727FD13FF272727FD09
-%FFA8272776FD04FF7D272752FFFF5227277DFD34FFA8FD052752FD0527A8
-%FD06FF7D27277DFF7D27277DFD12FF7D27277DFD08FF4B2727FD05FF2727
-%27FFFFA8272727FD34FFA8FD0B27A8FD08FF272727FFFF27F852FD12FFA8
-%272752FD07FFA852277CFD04FF7D27277DFFFF522727FD35FF4B27275227
-%272752272727A8FD09FFA8272752FF7D2727A1FD12FF522727FD07FF7D05
-%277DFD04FF270552FFFF7D272752FD34FFA8FD0A27A8FD0BFF520527A8FF
-%272727FD12FFA8272752FD06FF522727FD04FF7D27277DFFFF522752A8FD
-%35FFFD052752272727A8FD0CFFA827277DFF7D2727A8FD12FF522752FD05
-%FFA8270552FD04FF272727FFFFA8272752FD36FFA8FD0727A8FD0EFF4B27
-%27FFA827F852FD12FF522727A8FD04FF7D27277DFFFFFF7D27277DFFFF52
-%2727FD39FFFD0527A8FD0FFFA827277DFF522727FD12FFA8272776FD04FF
-%4B2727FD04FF520027A8FFA8272752FD39FFA8272727A8FD11FF520552FF
-%7D27277DFD12FF522752FD04FF52277DFD04FF272752FFFF7D27277DFD3B
-%FFA8FD13FF7D2727A8FF522776FD12FF522727FFFFFFA827277DFFFFFF7D
-%27277DFFFF522727FD50FFA8272752FF522727FD12FF7D27277DFFFF7D27
-%27FD04FF7D2752A8FFCB52277DFD51FF522752FF7D272EA1FD11FFA85227
-%7DFFFF522751FD04FF272752FFFFA827277DFD51FF522727A8A827007DFD
-%12FF272751FFFF272752FFFFFFA827277DFFFF522727FD52FFA827277DFF
-%272752FD12FF7D2752FFA827F87DFFFFFF7D27277DFFFF52F852FD52FFA8
-%520552FF520527A8FD11FF762727A8A827277DFFFFFF522727FFFFFF2727
-%52FD53FF522727FF7D2727FD12FFA827277D7D2727A8FFFFFF522752FFFF
-%7D27277DFD53FF7D2727A8A827277DFD11FFA827F87D7D2751A8FFFFFF27
-%2752FFFF7D2727A1FD53FF7D2727A8A852277DFD12FF272752522727FFFF
-%FFA227F87DFFFF522727FD54FFA827277DFF272752FD12FF520552522752
-%FFFFFFA8272E7DFFFF7D2752FD54FFA852277DFF522752FD12FF52274B27
-%2752FFFFFF7D2727A8FFFF522727FD55FF272752FF272752FD12FF52274B
-%52277DFFFFFF7D272DA8FFFF522752FD55FF52277DFF522752FD12FF52FD
-%042752FFFFFF522727FFFFFF272752FD55FF27274BFF522727FD12FF7DF8
-%2727277DFFFFFF7D2752FFFFFF52277DFD55FF522752FF7D2752FD12FF76
-%FD042752FFFFFF522727FFFFFF272752FD55FF522727FF522727FD12FF7D
-%052727277DFFFFFF522752FFFFFF522776FD55FF522752FF7D2752FD12FF
-%52FD0527A8FFA8272727FFFFFF272752FD55FF272752FF522727A8FD11FF
-%522752A827272752272727A8FFFFFF522752FD55FF52277DFFA827277DFD
-%11FF272752FF7D2727F827277DFD04FF512727FD55FF272752FFFF522752
-%FD10FF5227277DFFFFA8525252A8FD05FF7D2752FD54FFA827277DFFFF52
-%27277DFD0EFFA8522752FD0DFF522727FD54FFA827277DFFFFFF272727A8
-%FD0DFF5227277DFD0DFF7D2752A8FD53FF7D2727A8FFFFFFA8272727A8FD
-%0BFF52272752FD0EFF7D27277DFD53FF7D2727A8FD04FF7D2727277DA8FD
-%07FF7D2727274BFD10FF272752FD53FF522752FD06FF7D27272752527D7D
-%A87D7DFD042752FD11FF520552FD52FFA827F852FD07FFA82727F8FD0527
-%0527F82752FD12FF522727FD52FFA827277DFD09FF7D76FD0527524B7DFD
-%14FFA827277DFD51FF522727A8FD0CFFA8A87DA8A8FD16FFA8522752FD51
-%FF522752FD29FF4B2727FD50FFA8272752FD29FFA827277DFD4FFF522727
-%A8FD29FFA8522752FD4FFF520552FD2BFF522727A8FD4DFFA827277DFD2B
-%FFA8272752FD4DFF272727FD2DFF522727A8FD4BFF7D27277DFD2DFF7D27
-%2752FD4BFF520527A8FD2EFF522727A8FD49FFA8272752FD2FFFA8272727
-%FD49FF272727A8FD30FF7D27277DFD47FF5227277DFD31FFA84B2727A8FD
-%45FFA8272752FD33FF7D272752FD45FF5227277DFD34FF4B27277DFD43FF
-%52F82752FD36FF272727A8FD41FF7D272752FD37FFA8272727A8FD3FFFA8
-%272727FD39FF7D272727FD3EFFA8272727A8FD3AFF7D272727FD3CFFA827
-%27057DFD3CFF7D272727FD3AFFA82727277DFD3EFF52052727A8FD37FFA1
-%2727277DFD40FF7D272727A8FD35FF7D2727277DFD3FFFA8FFFF7D052727
-%7DFD33FF520527277DFD44FFA827272752A8FD2FFFA852272727A8FD46FF
-%A8272705277DFD2DFF52FD0427FD4AFF5227272752A8FD29FFA8FD042752
-%FD4CFF7D27F82727527DFD25FF7D52272727527DFD4FFF7D2727275252A8
-%FD21FFA852FD04277DFD52FFA852270527275252FD1CFFA85252FD042752
-%FD56FFA852FD0527527DA8FD15FFA87D52FD05277DA8FD59FFA852522727
-%F8272727527D7DA8A8FD07FFA8FFA8A87D7D522727270527275252A8FD5E
-%FFA85252FD07275227FD07522752FD07275252A8FD64FFA87C52FD0427F8
-%272727F8272727F8FD06275276A8FD6BFFA8A87D7D5276FD07527D7DA8A8
-%FDBDFFFF
+%524C45FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF
+%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFFFFFFFF
%%EndData
-endstream
endobj
56 0 obj
<>stream
-S近|>$@9XKӮ&OHP_isa64
HM36^b7wuu;lP)a3x]ua'+je@W8\}䨰I:]L'˛%j?
nn&?^
-Dֲt@Xj[é|V9*O+
VZ[(O{P'8`z68jEQȺ@>c[n50{ D1QV$t@0;({T:AYhoinVvaanKa=@O31]p/nTSGl¦]G|ut<MSq'q,W\oz5*5}З;9mo<~bVgPf
U-JMэp}lvX(U_Z|ոF0aIbS6;f͕KOX>ݝ
-o1V-*Zo llV̜lWG)kIlƿqzId2;{MC[4I{ZtI kk/}d7iǒFYCu"euvFW=kWkVE$fj*im.nW/翓n,
-@KFLtQVEлo=½3Ƭ.l
PvǶ~ҐN@I
-tP,<mI6:iFI\,3meKMOR?*4V0K>ox0
qב{LGL}{'wݶ`4`d[f|[@F#T䣼ncv90"}d1{:pU|j
-*dC=~WvR(:|`ڇvnlWE+wֈiUqd_֍F2*[&̬W8cr^@.2&{*5d{MMߚ+it/2nBV[Kd=;7NvԹC?HCx5+n|Z;صijA(F>mh S{IK#
-͎7bmB6`VMOIWJ-6"7!uKuDqzuD쥱|.ͧ)O%t_%' Dw;Rq}~d
-6Bm?DdkuKڵoPbkLn-Ϫ\Nwx_{1[Aco$ N:!w1yqanؓBh2~L;;%Cj4^jt`Sѭ[cơꨳSͮ9ʾi|q/mD}["tQ ߮}cd/r}H贊9N:S
ΨU
->+mjZ\k6L&J`ukӄxΠadR%IsՋLJt^Kܔ^,>g<"gt3<\D~
-LK
"fE@Kg6EɧVpx[ *g ,2H.s@ / t@;*&[ = Ym.0M D4x
- W+KM"<ؑ d i"@
-\͌XZ ܿJ &WE}
-`{SP Ih>(+}1&\5Fwu%K{̃'@v!
-d, .0o|R7C\4p6?WӪ \rQpWL0RFiiI٬"z |,(
Op@ -8 \CUMp|''\ V@
-\4<('kb*Wyt#B*=Ʈ}ys|'sFKj^%*GPޠAPZ@A:@ y)@O$- AtRb'7I? Is MUK6ǤF'r8:'GzL9HǓB˫W$R" h5[(3|
-]K>%hǍ>3xy7OjQgNdԇ *݆"r~x'
L֠ /@V#8@/yСK+VuvWlɳ>[GV7r<