From 6a48c1903dd2ff6981403c8fa5673e22f1e3c959 Mon Sep 17 00:00:00 2001 From: Lucius Bachmann Date: Tue, 26 Nov 2024 09:34:12 +0100 Subject: [PATCH] sw_concepts: rewrite abstractions.py to java --- topics/sw_concepts/code/abstractions.py | 120 ------------ .../pattern/examples/Abstractions.java | 134 +++++++++++++ .../examples/AbstractionsSolution.java | 177 ++++++++++++++++++ .../examples/BrightnessControlTest.java | 48 +++++ topics/sw_concepts/sw_concept_slides.md | 4 +- 5 files changed, 361 insertions(+), 122 deletions(-) delete mode 100644 topics/sw_concepts/code/abstractions.py create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java create mode 100644 topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java diff --git a/topics/sw_concepts/code/abstractions.py b/topics/sw_concepts/code/abstractions.py deleted file mode 100644 index ec590ca0..00000000 --- a/topics/sw_concepts/code/abstractions.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python -import errno -import math -import os -import sys -import time -from optparse import OptionParser - -# Censored hardware prototype2 and series (tsl2550) value range: 0 - 4015, daylight: 544 -# Censored hardware v3 (opt3001) value range: 0 - 36157, daylight: 290 -INPUT_MIN = 0 -INPUT_MAX_TSL2550 = 544 -INPUT_MAX_OPT3001 = 290 -INPUT_THRESHOLD = 11 - -# Censored hardware prototype2 (Intel backlight) value range: 0 - 937 -# Censored hardware series Device (Intel backlight) value range: 0 - 7500 -OUTPUT_MIN_FACTOR = 0.2 -OUTPUT_CHANGE_MAX_FACTOR = 0.005 - -sensor_value_lux_approx_map = [ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15, - 16, 18, 20, 22, 24, 26, 28, 30, - 32, 34, 36, 38, 40, 42, 44, 46, - 49, 53, 57, 61, 65, 69, 73, 77, - 81, 85, 89, 93, 97, 101, 105, 109, - 115, 123, 131, 139, 147, 155, 163, 171, - 179, 187, 195, 203, 211, 219, 227, 235, - 247, 263, 279, 295, 311, 327, 343, 359, - 375, 391, 407, 423, 439, 455, 471, 487, - 511, 543, 575, 607, 639, 671, 703, 735, - 767, 799, 831, 863, 895, 927, 959, 991, - 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, - 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, - 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, - 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 -] - - -def main(): - input_max = INPUT_MAX_TSL2550 - output_last_value = None - - parser = OptionParser() - _, args = parser.parse_args() - - if len(args) != 3: - raise Exception("wrong number of arguments") - - path = args[0] - input_path = args[1] - output_path = args[2] - opt3001 = "in_illuminance_input" in os.readlink(input_path) - - input_last_value = INPUT_MIN - INPUT_THRESHOLD - with open(path) as f: - output_max = int(f.readline().strip()) - output_min = int(math.ceil(output_max * OUTPUT_MIN_FACTOR)) - output_change_max = int(math.ceil(output_max * OUTPUT_CHANGE_MAX_FACTOR)) - - while True: - try: - if opt3001: - try: - with open(input_path) as f1: - input_value = float(f1.readline().strip()) - except IOError: - # This driver generates a read error if very little light is present - input_value = INPUT_MIN - else: - with open(input_path) as f: - input_value = int(f.readline().strip()) - if 0 <= input_value < len(sensor_value_lux_approx_map): - input_value = sensor_value_lux_approx_map[input_value] - else: - input_value = input_last_value - - input_value = min(input_value, input_max) - - # Ignore small input value changes - if abs(input_value - input_last_value) < INPUT_THRESHOLD: - input_value = input_last_value - - a = (input_value - INPUT_MIN) / float(input_max - INPUT_MIN) - value1 = int(a * float(output_max - output_min) + output_min) - output_value = min(value1, output_max) - - if output_last_value is None: - output_value = output_value - elif output_value >= output_last_value: - output_value = min(output_value, output_last_value + output_change_max) - else: - output_value = max(output_value, output_last_value - output_change_max) - dimmed_value = output_value - - if output_value != output_last_value: - print(f"input: %4i (%4.1f%%), output: %4i (%4.1f%%), dimmed: %4i (%4.1f%%)", - input_value, 100 * (input_value - INPUT_MIN) / float(input_max - INPUT_MIN), - output_value, 100 * (output_value - output_min) / float(output_max - output_min), - dimmed_value, 100 * (dimmed_value - output_min) / float(output_max - output_min)) - sys.stdout.flush() - - with open(output_path, "w") as f: - output_last_value = output_value - f.write(str(output_value)) - - input_last_value = input_value - except IOError as e: - # Ignore EGAIN errors which may happen if the I2C bus is busy - if e.errno != errno.EAGAIN: - raise e - - time.sleep(0.01) - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java new file mode 100644 index 00000000..5122f7bf --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java @@ -0,0 +1,134 @@ +package ch.scs.jumpstart.pattern.examples; + +import java.io.*; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class Abstractions { + + private static final int INPUT_MIN = 0; + private static final int INPUT_MAX_TSL2550 = 544; + private static final int INPUT_THRESHOLD = 11; + + private static final double OUTPUT_MIN_FACTOR = 0.2; + private static final double OUTPUT_CHANGE_MAX_FACTOR = 0.005; + + private static final int[] SENSOR_VALUE_LUX_APPROX_MAP = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 49, 53, 57, 61, 65, 69, 73, 77, + 81, 85, 89, 93, 97, 101, 105, 109, + 115, 123, 131, 139, 147, 155, 163, 171, + 179, 187, 195, 203, 211, 219, 227, 235, + 247, 263, 279, 295, 311, 327, 343, 359, + 375, 391, 407, 423, 439, 455, 471, 487, + 511, 543, 575, 607, 639, 671, 703, 735, + 767, 799, 831, 863, 895, 927, 959, 991, + 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, + 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, + 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, + 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 + }; + + @SuppressWarnings({ + "PMD.CognitiveComplexity", + "PMD.CyclomaticComplexity", + "PMD.SystemPrintln", + "PMD.AvoidPrintStackTrace" + }) + public static void main(String[] args) throws IOException { + if (args.length != 3) { + throw new IllegalArgumentException("Wrong number of arguments"); + } + + String path = args[0]; + String inputPath = args[1]; + String outputPath = args[2]; + boolean opt3001 = + Files.isSymbolicLink(Paths.get(inputPath)) + && Files.readSymbolicLink(Paths.get(inputPath)) + .toString() + .contains("in_illuminance_input"); + + int inputMax = INPUT_MAX_TSL2550; + int inputLastValue = INPUT_MIN - INPUT_THRESHOLD; + Integer outputLastValue = null; + + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { + int outputMax = Integer.parseInt(reader.readLine().trim()); + int outputMin = (int) Math.ceil(outputMax * OUTPUT_MIN_FACTOR); + int outputChangeMax = (int) Math.ceil(outputMax * OUTPUT_CHANGE_MAX_FACTOR); + + while (true) { + try { + int inputValue; + if (opt3001) { + try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { + inputValue = (int) Double.parseDouble(inputReader.readLine().trim()); + } catch (IOException e) { + inputValue = INPUT_MIN; + } + } else { + try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { + inputValue = Integer.parseInt(inputReader.readLine().trim()); + } + if (0 <= inputValue && inputValue < SENSOR_VALUE_LUX_APPROX_MAP.length) { + inputValue = SENSOR_VALUE_LUX_APPROX_MAP[inputValue]; + } else { + inputValue = inputLastValue; + } + } + + inputValue = Math.min(inputValue, inputMax); + + if (Math.abs(inputValue - inputLastValue) < INPUT_THRESHOLD) { + inputValue = inputLastValue; + } + + double a = (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN); + int value1 = (int) (a * (outputMax - outputMin) + outputMin); + int outputValue = Math.min(value1, outputMax); + + if (outputLastValue == null) { + outputValue = outputValue; + } else if (outputValue >= outputLastValue) { + outputValue = Math.min(outputValue, outputLastValue + outputChangeMax); + } else { + outputValue = Math.max(outputValue, outputLastValue - outputChangeMax); + } + int dimmedValue = outputValue; + + if (!Objects.equals(outputValue, outputLastValue)) { + System.out.printf( + "input: %4d (%4.1f%%), output: %4d (%4.1f%%), dimmed: %4d (%4.1f%%)%n", + inputValue, + 100 * (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN), + outputValue, + 100 * (outputValue - outputMin) / (double) (outputMax - outputMin), + dimmedValue, + 100 * (dimmedValue - outputMin) / (double) (outputMax - outputMin)); + System.out.flush(); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) { + outputLastValue = outputValue; + writer.write(String.valueOf(outputValue)); + } + + inputLastValue = inputValue; + } catch (IOException e) { + if (!(e instanceof FileNotFoundException)) { + throw e; + } + } + + TimeUnit.MILLISECONDS.sleep(10); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java new file mode 100644 index 00000000..b028bc61 --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/AbstractionsSolution.java @@ -0,0 +1,177 @@ +package ch.scs.jumpstart.pattern.examples; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.TimeUnit; + +public class AbstractionsSolution { + private static final int INPUT_MAX_TSL2550 = 544; + private static final double OUTPUT_MIN_FACTOR = 0.2; + private static final double OUTPUT_CHANGE_MAX_FACTOR = 0.005; + + @SuppressWarnings({"PMD.AvoidPrintStackTrace"}) + public static void main(String[] args) throws IOException { + if (args.length != 3) { + throw new IllegalArgumentException("Wrong number of arguments"); + } + + String path = args[0]; + String inputPath = args[1]; + String outputPath = args[2]; + boolean opt3001 = + Files.isSymbolicLink(Paths.get(inputPath)) + && Files.readSymbolicLink(Paths.get(inputPath)) + .toString() + .contains("in_illuminance_input"); + + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { + var brightnessControl = createBrightnessControl(reader, opt3001, inputPath); + + while (true) { + try { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) { + writer.write(String.valueOf(brightnessControl.update())); + } + } catch (IOException e) { + if (!(e instanceof FileNotFoundException)) { + throw e; + } + } + + TimeUnit.MILLISECONDS.sleep(10); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + private static BrightnessControl createBrightnessControl( + BufferedReader reader, boolean opt3001, String inputPath) throws IOException { + int outputMax = Integer.parseInt(reader.readLine().trim()); + int outputMin = (int) Math.ceil(outputMax * OUTPUT_MIN_FACTOR); + int outputChangeMax = (int) Math.ceil(outputMax * OUTPUT_CHANGE_MAX_FACTOR); + + return new BrightnessControl( + opt3001, INPUT_MAX_TSL2550, inputPath, outputMin, outputMax, outputChangeMax); + } +} + +class BrightnessControl { + private static final int INPUT_MIN = 0; + private static final int INPUT_THRESHOLD = 11; + + private static final int[] SENSOR_VALUE_LUX_APPROX_MAP = { + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, + 49, 53, 57, 61, 65, 69, 73, 77, + 81, 85, 89, 93, 97, 101, 105, 109, + 115, 123, 131, 139, 147, 155, 163, 171, + 179, 187, 195, 203, 211, 219, 227, 235, + 247, 263, 279, 295, 311, 327, 343, 359, + 375, 391, 407, 423, 439, 455, 471, 487, + 511, 543, 575, 607, 639, 671, 703, 735, + 767, 799, 831, 863, 895, 927, 959, 991, + 1039, 1103, 1167, 1231, 1295, 1359, 1423, 1487, + 1551, 1615, 1679, 1743, 1807, 1871, 1935, 1999, + 2095, 2223, 2351, 2479, 2607, 2735, 2863, 2991, + 3119, 3247, 3375, 3503, 3631, 3759, 3887, 4015 + }; + + private final boolean opt3001; + private final int inputMax; + private final String inputPath; + + private final int outputMax; + private final int outputMin; + private final int outputChangeMax; + + private int inputLastValue; + private Integer outputLastValue = null; + + public BrightnessControl( + boolean opt3001, + int inputMax, + String inputPath, + int outputMin, + int outputMax, + int outputChangeMax) { + this.opt3001 = opt3001; + this.inputMax = inputMax; + this.inputPath = inputPath; + this.outputMax = outputMax; + this.outputMin = outputMin; + this.outputChangeMax = outputChangeMax; + + this.inputLastValue = INPUT_MIN - INPUT_THRESHOLD; + } + + int update() throws IOException { + var inputValue = getInputValue(); + var outputValue = calculateOutput(inputValue); + var dimmedValue = getDimmedValue(outputValue); + logValues(inputValue, outputValue, dimmedValue); + inputLastValue = inputValue; + outputLastValue = dimmedValue; + return dimmedValue; + } + + private int getInputValue() throws IOException { + int inputValue; + if (opt3001) { + try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { + inputValue = (int) Double.parseDouble(inputReader.readLine().trim()); + } catch (IOException e) { + inputValue = INPUT_MIN; + } + } else { + try (BufferedReader inputReader = new BufferedReader(new FileReader(inputPath))) { + inputValue = Integer.parseInt(inputReader.readLine().trim()); + } + if (0 <= inputValue && inputValue < SENSOR_VALUE_LUX_APPROX_MAP.length) { + inputValue = SENSOR_VALUE_LUX_APPROX_MAP[inputValue]; + } else { + inputValue = inputLastValue; + } + } + + inputValue = Math.min(inputValue, inputMax); + + if (Math.abs(inputValue - inputLastValue) < INPUT_THRESHOLD) { + inputValue = inputLastValue; + } + return inputValue; + } + + private int calculateOutput(int inputValue) { + double a = (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN); + int value1 = (int) (a * (outputMax - outputMin) + outputMin); + return Math.min(value1, outputMax); + } + + private int getDimmedValue(int outputValue) { + if (outputLastValue == null) { + return outputValue; + } + if (outputValue >= outputLastValue) { + return Math.min(outputValue, outputLastValue + outputChangeMax); + } else { + return Math.max(outputValue, outputLastValue - outputChangeMax); + } + } + + @SuppressWarnings({"PMD.SystemPrintln"}) + private void logValues(int inputValue, int outputValue, int dimmedValue) { + System.out.printf( + "input: %4d (%4.1f%%), output: %4d (%4.1f%%), dimmed: %4d (%4.1f%%)%n", + inputValue, + 100 * (inputValue - INPUT_MIN) / (double) (inputMax - INPUT_MIN), + outputValue, + 100 * (outputValue - outputMin) / (double) (outputMax - outputMin), + dimmedValue, + 100 * (dimmedValue - outputMin) / (double) (outputMax - outputMin)); + System.out.flush(); + } +} diff --git a/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java new file mode 100644 index 00000000..7bd55afb --- /dev/null +++ b/topics/sw_concepts/code/pattern-examples/src/test/java/ch/scs/jumpstart/pattern/examples/BrightnessControlTest.java @@ -0,0 +1,48 @@ +package ch.scs.jumpstart.pattern.examples; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class BrightnessControlTest { + public static final int INPUT_MAX = 4015; + public static final int OUTPUT_MAX = 1000; + public static final int OUTPUT_MIN = 0; + public static final int OUTPUT_CHANGE_MAX = 10; + + @TempDir private Path tempDir; + + private Path input; + + @BeforeEach + void setup() { + input = tempDir.resolve("input"); + } + + @Test + void climbs_to_target_value_in_steps() throws IOException { + var brightnessControl = + new BrightnessControl( + false, INPUT_MAX, input.toString(), OUTPUT_MIN, OUTPUT_MAX, OUTPUT_CHANGE_MAX); + Files.writeString(input, "0"); + brightnessControl.update(); + + Files.writeString(input, "60"); + + var outputValues = new ArrayList<>(); + for (int __ : IntStream.range(0, 10).toArray()) { + int update = brightnessControl.update(); + outputValues.add(update); + } + + assertThat(outputValues).isEqualTo(List.of(10, 20, 30, 40, 50, 52, 52, 52, 52, 52)); + } +} diff --git a/topics/sw_concepts/sw_concept_slides.md b/topics/sw_concepts/sw_concept_slides.md index 89fdd965..20798fb3 100644 --- a/topics/sw_concepts/sw_concept_slides.md +++ b/topics/sw_concepts/sw_concept_slides.md @@ -8,9 +8,9 @@ date: \today Was ist hier falsch? ------- -Code Beispiel: [abstractions.py](code/abstractions.py) +Code Beispiel: [Abstractions.java](code/pattern-examples/src/main/java/ch/scs/jumpstart/pattern/examples/Abstractions.java) -abstractions.py Zusammenfassung +Abstractions.java Zusammenfassung ------- * Fast keine Strukturierung durch Methoden/Funktionen