Example Major Project

A smart greenhouse controller built and documented as an HSC major project, from the brief and requirements to class design, algorithms, testing and evaluation. It runs live in your browser.

Year 12 All 7 focus areas Outcomes: SE-12-01 to SE-12-09
Part 1 See it running

Live Demo

This is the real control code running in your browser. The greenhouse itself is simulated: the physics model stands in for the hardware, so the sensors, controller, model and security code all behave as they would on a microcontroller. One real second is ten simulated minutes at 1× speed.

Things to try Set the weather to Heatwave and watch the fan and the trend chart. Break the moisture sensor and see the controller stop watering. Sign in as tech with the password Gr0w-Safe!, then try saving a low mark of <script> or 70.
Day 106:00Daylight

All systems normal

Temperature– °C
Soil moisture– %
Light– luxLamp below 250 lux
Humidity– %Monitored only

Actuators Mode: Automatic

Pumpoff0 kWh
Fanoff0 kWh
Grow lampoff0 kWh
Water tank120 L

Last 24 hours Temperature Soil moisture Target band

Watering forecast (machine learning)

Model
Collecting data…
Samples
0
R²
—
MSE
—
Next watering
—

Access Guest: view only

Scenario

Data log

Search the log by time, or sort it.

Event log

    Part 2 Identifying and defining

    The Brief

    The school's agriculture class grows seedlings in a small greenhouse. Plants die over long weekends and holidays because nobody is there to water them, open the vents on hot days or switch on the grow lamp in winter. Students also want to see what is happening inside the greenhouse from a classroom computer.

    Problem statement Design and build a controller that keeps soil moisture and temperature in a safe range without a person present, shows live conditions on a web dashboard, and lets only an authorised technician change its settings.

    Stakeholders

    • Agriculture teacher: the client. Needs healthy plants and a record of conditions for lessons.
    • Students: view the dashboard, but must not be able to change settings.
    • Technician: sets targets, runs the system by hand for maintenance and refills the tank.

    Constraints

    • Hardware budget under $150: a microcontroller with Wi-Fi, a capacitive soil moisture sensor, a temperature and humidity sensor, a light sensor, a relay board, a 12 V pump, a fan and an LED grow lamp.
    • Must fail safe: a broken sensor or an empty tank must never flood the beds or burn out the pump.
    • Ten weeks of development time, working alone.

    Requirements

    Each requirement is written so it can be tested. The last column names the test that proves it, so the evaluation at the end can point to evidence rather than opinion.

    IDFunctional requirementEvidence
    F1Water the beds when soil moisture falls below the low mark and stop at the high mark.T3, T4, T5
    F2Run the fan when the temperature rises above the maximum, and stop 3 °C below it.Live demo: try a heatwave
    F3Run the grow lamp during growing hours (06:00–20:00) when light is low.Live demo: try an overcast day
    F4Detect an out-of-range sensor reading, stop watering and raise an alert.T1, T2, T6
    F5Log readings every 10 minutes for 7 days; search by time, list the hottest readings and export CSV.T9, T10, T11
    F6Predict when the next watering is due from the conditions so far.T8
    F7Only a signed-in technician can change setpoints, switch to manual mode or refill the tank.T12–T17
    IDNon-functional requirementEvidence
    N1Dashboard updates at least once a second.Live demo refreshes 10 times a second
    N2Passwords are never stored or logged in plain text.Salted SHA-256 hashes; see the sign-in code
    N3Sign-in locks for 20 seconds after three wrong attempts.T16, T17
    N4Usable by keyboard and screen reader; works on a phone.Labelled controls, live-region log, responsive layout
    N5Every setting change is recorded with who made it.Event log (AUTH and CTRL entries)
    Part 3 Research, planning and design

    Where Each Topic Appears

    A strong major project draws on more than one focus area. This one uses all seven. Each card names what the project borrows from that topic and where to see it in the demo.

    Class Design

    Each class has one job. The controller only decides: it returns the actuator states it wants and never switches hardware itself. That separation is what makes it possible to unit test the control logic without a greenhouse.

    ClassResponsibilityKey methodsRelationship
    SensorTake a reading and check it is physically possibleread(), measure(), inRange()Abstract parent
    TemperatureSensor, MoistureSensor, LightSensor, HumiditySensorMeasure one quantitymeasure() (overridden)Inherit from Sensor
    ActuatorSwitch a device and track its energy useset(), energyWh()Used by Greenhouse
    ControllerTurn readings into decisions; validate setpointsdecide(), validateSetpoints()Used by Greenhouse
    DataLoggerKeep 7 days of records; search, sort and export themfindByTime(), mergeSort(), toCSV()Used by Greenhouse
    LinearRegressionLearn evaporation rate from temperatureadd(), fit(), predict()Used by Greenhouse
    AuthServiceStore salted hashes, check sign-ins, enforce lockoutaddUser(), login()Used by the dashboard
    GreenhouseRun one minute: sense, decide, act, record, learntick(), forecastWatering()Composed of all of the above
    Python — Inheritance and polymorphism in the sensors
    class Sensor:
        def __init__(self, name, minimum, maximum):
            self.name = name
            self.minimum = minimum
            self.maximum = maximum
            self.valid = False
    
        def measure(self):
            raise NotImplementedError   # each subclass reads its own hardware
    
        def read(self):
            value = self.measure()
            self.valid = self.minimum <= value <= self.maximum
            return value
    
    
    class MoistureSensor(Sensor):
        def __init__(self, pin):
            super().__init__("Soil moisture", 0, 100)
            self.pin = pin
    
        def measure(self):
            raw = self.pin.read_u16()              # 0–65535 from the ADC
            return (52000 - raw) / 300      # calibrated: dry air 0 %, water 100 %

    The demo runs the same design in JavaScript so it can run in a browser. On the real hardware, MicroPython runs the Python version above.

    Key Algorithms

    Control loop with hysteresis

    Switching the pump on below 35% and off at the same value would make it chatter on and off as the reading wobbles. Using two marks, a low one to start and a high one to stop, gives a dead band in between where nothing changes.

    Pseudocode — One control cycle
    BEGIN ControlCycle
        READ moisture, temperature, light
        IF moisture reading is outside 0–100 THEN
            pump ← OFF
            RAISE ALERT "Moisture sensor fault"
        ELSE
            IF tank level = 0 THEN
                pump ← OFF
                RAISE ALERT "Tank empty"
            ELSE
                IF moisture < lowMark THEN
                    pump ← ON
                ELSE
                    IF moisture ≥ highMark THEN
                        pump ← OFF
                    ENDIF
                ENDIF
            ENDIF
        ENDIF
        IF temperature > maxTemp THEN
            fan ← ON
        ELSE
            IF temperature < maxTemp − 3 THEN
                fan ← OFF
            ENDIF
        ENDIF
        IF hour is between 06:00 and 20:00 AND light < 250 THEN
            lamp ← ON
        ELSE
            IF hour is outside 06:00–20:00 OR light > 700 THEN
                lamp ← OFF
            ENDIF
        ENDIF
        WRITE log record every 10 minutes
    END ControlCycle

    Learning the evaporation rate

    Soil dries faster when it is hot. While the pump is off, the system measures how fast moisture falls over each 10 minutes and pairs it with the temperature at the time. A least-squares line through those points predicts the drying rate at any temperature, which gives the time until the next watering. This is supervised learning: every sample has a known answer, the measured rate.

    Python — Least-squares regression
    def fit_line(xs, ys):
        """Return slope a, intercept b and R² for y = a*x + b."""
        n = len(xs)
        mean_x = sum(xs) / n
        mean_y = sum(ys) / n
        sxy = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys))
        sxx = sum((x - mean_x) ** 2 for x in xs)
        a = sxy / sxx
        b = mean_y - a * mean_x
        ss_res = sum((y - (a * x + b)) ** 2 for x, y in zip(xs, ys))
        ss_tot = sum((y - mean_y) ** 2 for y in ys)
        return a, b, 1 - ss_res / ss_tot
    
    
    def minutes_until_watering(moisture, low_mark, temperature, a, b):
        rate = a * temperature + b             # predicted % lost per minute
        return (moisture - low_mark) / rate

    Signing in safely

    The system stores a random salt and the SHA-256 hash of salt plus password, never the password. Wrong usernames and wrong passwords get the same message, so an attacker can't learn which accounts exist, and three failures lock sign-in for 20 seconds.

    Python — Salted hash with lockout
    import hashlib, secrets, time
    
    users = {}                 # username -> (salt, hash, role)
    failures, locked_until = 0, 0
    
    def add_user(username, password, role):
        salt = secrets.token_hex(8)
        digest = hashlib.sha256((salt + password).encode()).hexdigest()
        users[username] = (salt, digest, role)
    
    def login(username, password):
        global failures, locked_until
        if time.time() < locked_until:
            return None, "Too many attempts. Try again soon."
        salt, digest, role = users.get(username, ("x", None, None))
        if hashlib.sha256((salt + password).encode()).hexdigest() == digest:
            failures = 0
            return role, "Signed in"
        failures += 1
        if failures >= 3:
            failures, locked_until = 0, time.time() + 20
        return None, "Incorrect username or password."
    Good enough for a school project, not for production A real system would use a slow hashing algorithm built for passwords, such as bcrypt or Argon2, so that stolen hashes take far longer to crack.

    Data Dictionary

    One record is written to the log every 10 simulated minutes. The log holds 1,008 records (7 days); the oldest record is dropped when a new one arrives. Records stay in time order, which is what makes binary search possible.

    FieldData typeFormat / unitValidationExample
    timeIntegerMinutes since startMultiple of 10, increasing1560 (Day 2 02:00)
    tempReal°C, 1 decimal place−10 to 60, else sensor fault24.6
    moistureReal% volumetric0 to 100, else sensor fault41.0
    lightReallux0 to 2000612
    humidityReal% relative0 to 10058
    pump, fan, lampBoolean1 = on, 0 = off0 or 11
    Part 4 Testing and evaluating

    Test Results

    These unit tests run the real classes in your browser, separately from the live demo. Each uses a chosen category of test data: normal data the system should accept, boundary data at the exact edge of a range, and erroneous data it must reject.

    Not run yet
    TestDescriptionTest dataExpectedActualResult
    Press “Run the tests” to fill this table.

    Evaluation

    The evaluation goes back to the brief and the requirements, and judges each one against evidence.

    RequirementMet?Evidence and comment
    F1 WateringYesT3–T5 pass. The trend chart shows moisture held inside the target band across day and night.
    F2 VentingYesIn a heatwave the fan runs through the afternoon, but the temperature can still pass the maximum: the fan alone is not enough on the hottest days.
    F4 Fault handlingYesT6 passes; breaking the sensor stops watering and raises an alert within one cycle.
    F6 ForecastPartlyR² settles between about 0.75 and 0.8 after a day of data. The model uses temperature only, and sunshine raises both temperature and drying, so the slope comes out slightly high and forecasts are least accurate at dawn and on overcast days.
    F7, N2, N3 SecurityYesT12–T17 pass. Passwords are hashed with a salt; lockout works. See the note above on production hashing.
    N4 AccessibilityYesAll controls are buttons or labelled inputs, alerts use a live region, and the layout stacks on a phone.

    Improvements for the next version

    • Add light as a second feature in the model (multiple linear regression) to improve dawn forecasts.
    • Add shade cloth on a servo for heatwaves, since venting alone can't hold the maximum.
    • Send a phone notification when the tank is low, rather than only showing an alert.
    • Replace SHA-256 with a password-hashing algorithm such as bcrypt when moving to a real server.
    Using this as a model for your own project Notice the thread from brief to requirements to tests to evaluation: every requirement has an ID, every test names the requirement it proves, and the evaluation quotes the evidence. Markers look for that link. Your topic can be completely different; the structure is what transfers.