From 0183d50c46d4e1f0306cb986fb9046d4367c6cee Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 28 Feb 2021 13:01:43 +0100 Subject: [PATCH] Implemented digital outputs and inputs in Arduino sketch --- Arduino/MassiveKnob/MassiveKnob.ino | 161 ++++++++++++++---- .../GetMuted/DeviceGetMutedAction.cs | 2 + .../DeviceGetMutedActionSettingsView.xaml | 2 + .../GetVolume/DeviceGetVolumeAction.cs | 2 + .../DeviceSetMutedActionSettingsView.xaml | 3 + .../Devices/EmulatorDevice.cs | 2 +- .../Devices/SerialDevice.cs | 6 +- .../Worker/SerialWorker.cs | 37 ++-- .../Model/MassiveKnobOrchestrator.cs | 3 + Windows/MassiveKnob/Program.cs | 1 + 10 files changed, 174 insertions(+), 45 deletions(-) diff --git a/Arduino/MassiveKnob/MassiveKnob.ino b/Arduino/MassiveKnob/MassiveKnob.ino index 12f237b..d59e095 100644 --- a/Arduino/MassiveKnob/MassiveKnob.ino +++ b/Arduino/MassiveKnob/MassiveKnob.ino @@ -7,18 +7,39 @@ // Set this to the number of potentiometers you have connected const byte AnalogInputCount = 1; -// For each potentiometer, specify the port +// Set this to the number of buttons you have connected +const byte DigitalInputCount = 1; + +// 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; + + +// For each potentiometer, specify the pin const byte AnalogInputPin[AnalogInputCount] = { A0 }; -// Minimum time between reporting changing values, reduces serial traffic +// 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 +}; + + +// Minimum time between reporting changing values, reduces serial traffic and debounces digital inputs const unsigned long MinimumInterval = 50; -// Alpha value of the Exponential Moving Average (EMA) to reduce noise +// Alpha value of the Exponential Moving Average (EMA) for analog inputs to reduce noise const float EMAAlpha = 0.6; -// How many measurements to take at boot time to seed the EMA +// How many measurements to take at boot time for analog inputs to seed the EMA const byte EMASeedCount = 5; @@ -34,6 +55,7 @@ const byte EMASeedCount = 5; #include "./min.c" +// MIN protocol context and callbacks struct min_context minContext; uint16_t min_tx_space(uint8_t port) { return 512U; } @@ -46,6 +68,7 @@ void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_p void min_tx_start(uint8_t port) {} void min_tx_finished(uint8_t port) {} + const uint8_t FrameIDHandshake = 42; const uint8_t FrameIDHandshakeResponse = 43; const uint8_t FrameIDAnalogInput = 1; @@ -56,13 +79,24 @@ const uint8_t FrameIDQuit = 62; const uint8_t FrameIDError = 63; -bool active = false; +struct AnalogInputStatus +{ + byte Value; + unsigned long LastChange; + int ReadValue; + float EMAValue; +}; -byte analogValue[AnalogInputCount]; -unsigned long lastChange[AnalogInputCount]; -int analogReadValue[AnalogInputCount]; -float emaValue[AnalogInputCount]; -unsigned long lastPlot; +struct DigitalInputStatus +{ + bool Value; + unsigned long LastChange; +}; + + +bool active = false; +struct AnalogInputStatus analogInputStatus[AnalogInputCount]; +struct DigitalInputStatus digitalInputStatus[AnalogInputCount]; void setup() @@ -77,23 +111,38 @@ void setup() min_init_context(&minContext, 0); - // Seed the moving average - for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++) + // Seed the moving average for analog inputs + for (byte i = 0; i < AnalogInputCount; i++) { - pinMode(AnalogInputPin[analogInputIndex], INPUT); - emaValue[analogInputIndex] = analogRead(AnalogInputPin[analogInputIndex]); + pinMode(AnalogInputPin[i], INPUT); + analogInputStatus[i].EMAValue = analogRead(AnalogInputPin[i]); } - for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++) + for (byte i = 0; i < AnalogInputCount; i++) for (byte seed = 1; seed < EMASeedCount - 1; seed++) - getAnalogValue(analogInputIndex); + getAnalogValue(i); - // Read the initial values - for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++) + // Read the initial stabilized values + for (byte i = 0; i < AnalogInputCount; i++) { - analogValue[analogInputIndex] = getAnalogValue(analogInputIndex); - lastChange[analogInputIndex] = millis(); + analogInputStatus[i].Value = getAnalogValue(i); + analogInputStatus[i].LastChange = millis(); + } + + + // Set up digital inputs and outputs + for (byte i = 0; i < DigitalInputCount; i++) + { + pinMode(DigitalInputPin[i], INPUT_PULLUP); + digitalInputStatus[i].Value = getDigitalValue(i); + digitalInputStatus[i].LastChange = millis(); + } + + for (byte i = 0; i < DigitalOutputCount; i++) + { + pinMode(DigitalOutputPin[i], OUTPUT); + digitalWrite(DigitalOutputPin[i], LOW); } } @@ -108,18 +157,36 @@ void loop() // Check analog inputs byte newAnalogValue; - for (byte analogInputIndex = 0; analogInputIndex < AnalogInputCount; analogInputIndex++) + for (byte i = 0; i < AnalogInputCount; i++) { - newAnalogValue = getAnalogValue(analogInputIndex); + newAnalogValue = getAnalogValue(i); - if (newAnalogValue != analogValue[analogInputIndex] && (millis() - lastChange[analogInputIndex] >= MinimumInterval)) + if (newAnalogValue != analogInputStatus[i].Value && (millis() - analogInputStatus[i].LastChange >= MinimumInterval)) { if (active) // Send out new value - outputAnalogValue(analogInputIndex, newAnalogValue); + outputAnalogValue(i, newAnalogValue); - analogValue[analogInputIndex] = newAnalogValue; - lastChange[analogInputIndex] = millis(); + analogInputStatus[i].Value = newAnalogValue; + analogInputStatus[i].LastChange = millis(); + } + } + + + // Check digital inputs + bool newDigitalValue; + for (byte i = 0; i < DigitalInputCount; i++) + { + newDigitalValue = getDigitalValue(i); + + if (newDigitalValue != digitalInputStatus[i].Value && (millis() - digitalInputStatus[i].LastChange >= MinimumInterval)) + { + if (active) + // Send out new value + outputDigitalValue(i, newDigitalValue); + + digitalInputStatus[i].Value = newDigitalValue; + digitalInputStatus[i].LastChange = millis(); } } } @@ -138,7 +205,7 @@ void min_application_handler(uint8_t min_id, uint8_t *min_payload, uint8_t len_p break; case FrameIDDigitalOutput: - //processDigitalOutputMessage(); + processDigitalOutputMessage(min_payload, len_payload); break; case FrameIDQuit: @@ -165,12 +232,28 @@ void processHandshakeMessage(uint8_t *min_payload, uint8_t len_payload) return; } - byte payload[4] { AnalogInputCount, 0, 0, 0 }; + byte payload[4] { AnalogInputCount, DigitalInputCount, AnalogOutputCount, DigitalOutputCount }; if (min_queue_frame(&minContext, FrameIDHandshakeResponse, (uint8_t *)payload, 4)) active = true; } +void processDigitalOutputMessage(uint8_t *min_payload, uint8_t len_payload) +{ + if (len_payload < 2) + { + outputError("Invalid digital output payload length"); + return; + } + + byte outputIndex = min_payload[0]; + if (outputIndex < DigitalOutputCount) + digitalWrite(DigitalOutputPin[min_payload[0]], min_payload[1] == 0 ? LOW : HIGH); + else + outputError("Invalid digital output index: " + String(outputIndex)); +} + + void processQuitMessage() { active = false; @@ -184,10 +267,19 @@ byte getAnalogValue(byte analogInputIndex) // Give the ADC some time to stabilize delay(10); - analogReadValue[analogInputIndex] = analogRead(AnalogInputPin[analogInputIndex]); - emaValue[analogInputIndex] = (EMAAlpha * analogReadValue[analogInputIndex]) + ((1 - EMAAlpha) * emaValue[analogInputIndex]); + int readValue = analogRead(AnalogInputPin[analogInputIndex]); + analogInputStatus[analogInputIndex].ReadValue = readValue; - return map(emaValue[analogInputIndex], 0, 1023, 0, 100); + int newEMAValue = (EMAAlpha * readValue) + ((1 - EMAAlpha) * analogInputStatus[analogInputIndex].EMAValue); + analogInputStatus[analogInputIndex].EMAValue = newEMAValue; + + return map(newEMAValue, 0, 1023, 0, 100); +} + + +bool getDigitalValue(byte digitalInputIndex) +{ + return digitalRead(DigitalInputPin[digitalInputIndex]) == LOW; } @@ -198,6 +290,13 @@ void outputAnalogValue(byte analogInputIndex, byte newValue) } +void outputDigitalValue(byte digitalInputIndex, bool newValue) +{ + byte payload[2] = { digitalInputIndex, newValue ? 1 : 0 }; + min_send_frame(&minContext, FrameIDDigitalInput, (uint8_t *)payload, 2); +} + + void outputError(String message) { min_send_frame(&minContext, FrameIDError, (uint8_t *)message.c_str(), message.length()); diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs index dde700e..a20b448 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedAction.cs @@ -6,6 +6,8 @@ using Microsoft.Extensions.Logging; namespace MassiveKnob.Plugin.CoreAudio.GetMuted { + // TODO send out initial muted state after proper initialization + public class DeviceGetMutedAction : IMassiveKnobAction { public Guid ActionId { get; } = new Guid("86646ca7-f472-4c5a-8d0f-7e5d2d162ab9"); diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml index aef700b..85c6576 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetMuted/DeviceGetMutedActionSettingsView.xaml @@ -10,5 +10,7 @@ d:DataContext="{d:DesignInstance getMuted:DeviceGetMutedActionSettingsViewModel}"> + + diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs index c929900..1bc27b1 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs +++ b/Windows/MassiveKnob.Plugin.CoreAudio/GetVolume/DeviceGetVolumeAction.cs @@ -6,6 +6,8 @@ using Microsoft.Extensions.Logging; namespace MassiveKnob.Plugin.CoreAudio.GetVolume { + // TODO send out initial volume after proper initialization + public class DeviceGetVolumeAction : IMassiveKnobAction { public Guid ActionId { get; } = new Guid("6ebf91af-8240-4a75-9729-c6a1eb60dcba"); diff --git a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml index 4cae53d..3a73fce 100644 --- a/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml +++ b/Windows/MassiveKnob.Plugin.CoreAudio/SetMuted/DeviceSetMutedActionSettingsView.xaml @@ -10,5 +10,8 @@ d:DataContext="{d:DesignInstance setMuted:DeviceSetMutedActionSettingsViewModel}"> + + + diff --git a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs index 05c089c..4c70f5e 100644 --- a/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs +++ b/Windows/MassiveKnob.Plugin.EmulatorDevice/Devices/EmulatorDevice.cs @@ -85,7 +85,7 @@ namespace MassiveKnob.Plugin.EmulatorDevice.Devices } - public void SetDigitalOutput(int digitalOutputIndex, bool @on) + public void SetDigitalOutput(int digitalOutputIndex, bool on) { if (digitalOutputIndex >= windowViewModel.DigitalOutputCount) return; diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs index 40e62a6..c30a549 100644 --- a/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Devices/SerialDevice.cs @@ -72,12 +72,12 @@ namespace MassiveKnob.Plugin.SerialDevice.Devices public void SetAnalogOutput(int analogOutputIndex, byte value) { - // TODO Support SetAnalogOutput + worker.SetAnalogOutput(analogOutputIndex, value); } - public void SetDigitalOutput(int digitalOutputIndex, bool @on) + public void SetDigitalOutput(int digitalOutputIndex, bool on) { - // TODO Support SetDigitalOutput + worker.SetDigitalOutput(digitalOutputIndex, on); } } } diff --git a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs index 02b11ea..87a567d 100644 --- a/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs +++ b/Windows/MassiveKnob.Plugin.SerialDevice/Worker/SerialWorker.cs @@ -19,6 +19,21 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker private int lastBaudRate; private bool lastDtrEnable; + + private enum MassiveKnobFrameID + { + Handshake = 42, + HandshakeResponse = 43, + AnalogInput = 1, + DigitalInput = 2, + AnalogOutput = 3, + DigitalOutput = 4, + Quit = 62, + Error = 63 + } + + + public SerialWorker(IMassiveKnobDeviceContext context, ILogger logger) { this.context = context; @@ -58,17 +73,19 @@ namespace MassiveKnob.Plugin.SerialDevice.Worker } - private enum MassiveKnobFrameID + public void SetAnalogOutput(int analogOutputIndex, byte value) { - Handshake = 42, - HandshakeResponse = 43, - AnalogInput = 1, - DigitalInput = 2, - AnalogOutput = 3, - DigitalOutput = 4, - Quit = 62, - Error = 63 - } + minProtocol?.QueueFrame( + (byte)MassiveKnobFrameID.AnalogOutput, + new [] { (byte)analogOutputIndex, value }); + } + + public void SetDigitalOutput(int digitalOutputIndex, bool on) + { + minProtocol?.QueueFrame( + (byte)MassiveKnobFrameID.DigitalOutput, + new [] { (byte)digitalOutputIndex, on ? (byte)1 : (byte)0 }); + } private void MinProtocolOnOnConnected(object sender, EventArgs e) diff --git a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs index 1fa0b82..4068392 100644 --- a/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs +++ b/Windows/MassiveKnob/Model/MassiveKnobOrchestrator.cs @@ -272,6 +272,8 @@ namespace MassiveKnob.Model } + // TODO store output values for when the device connects and should receive initial values + protected void AnalogChanged(IMassiveKnobDeviceContext context, int analogInputIndex, byte value) { if (context != activeDeviceContext) @@ -525,6 +527,7 @@ namespace MassiveKnob.Model public void Connected(DeviceSpecs specs) { // TODO update status ? + // TODO send out initial values for outputs owner.UpdateActiveDeviceSpecs(this, specs); } diff --git a/Windows/MassiveKnob/Program.cs b/Windows/MassiveKnob/Program.cs index 4566fee..cb118c8 100644 --- a/Windows/MassiveKnob/Program.cs +++ b/Windows/MassiveKnob/Program.cs @@ -10,6 +10,7 @@ using Serilog.Core; using Serilog.Events; using SimpleInjector; + namespace MassiveKnob { public static class Program