![]() |
rasProject_01 / weSweetHome
R. 102 2025-10-31
process control / RasPi software by weinert-automation
|
Process control for a modestly smart home. More...
#include "arch/config.h"#include "sweetHome2.h"#include "weUtil.h"#include "weStateM.h"#include "weLockWatch.h"#include "weEcarLd.h"#include "weShareMem.h"#include "weAR_N4105.h"#include <errno.h>#include <sys/sem.h>#include <getopt.h>#include <weModbus.h>#include "sweetHomeLocal.h"Functions | |
| void | batCntBalStatChg (state_t *const me) |
| Battery as controlled ballast SFC state changes. More... | |
| void | batCntLodStatChg (state_t *const me) |
| Battery controlled load or keep; SFC state changes. More... | |
| void | batUnloadStatChg (state_t *const me) |
| Battery unload SFC state changes. More... | |
| void | genHWPstateText (char *stateText, state_t const *const me, char const *stamp) |
| Hot water comfort pump control timer state text function. | |
| void | hotWatPmpCntlTimChg (state_t *const me) |
| Hot water comfort pump control timer state change function. More... | |
| void | hotWatPmpSchTimChg (state_t *const me) |
| Hot water comfort pump schedule timer state change function. More... | |
| void | hotWpmpButChg (state_t *const me) |
| Hot water comfort pump button state change. More... | |
| void | initDawnDusk (void) |
| Adjust dawn and dusk timers. More... | |
| void | initSunRiSet (int init) |
| Initialise dawn, sunrise, sunset and dusk timers. More... | |
| void | load250WstateChg (state_t *const me) |
| Controlled fixed surplus consumption; SFC state changes. More... | |
| int | main (int argc, char **argv) |
| The program. More... | |
| void * | processIOthread (void *args) |
| The task of controlling process IO. More... | |
| int | readRetFil (void) |
| Read (day start) retain file. More... | |
| void | switchARN4105 (uint8_t const cutOff) |
| AR-N 4105 implementation function. More... | |
| int | writeDayFil (void) |
| Append daily entry work table. More... | |
| int | writeRetFil (void) |
| Write (day start) retain file. More... | |
Variables | |
| state_t | batCntBalSeq |
| Battery controlled load as ballast. | |
| state_t | batCntLodSeq |
| Battery loading and keeping. | |
| state_t | batUnloadSeq |
| Battery controlled unload via inverter. | |
| const char * | dayFileFormat |
| Format string for the file dayFilNam. More... | |
| const char * | dayFileHeader |
| CSV header of the file dayFilNam. More... | |
| volatile off_t | dayFileSize |
| The size of the work table before last/actual daily entry. More... | |
| __time_t const | hotTimSched [] |
| Schedule times for hot water comfort pump starts. More... | |
| state_t | hotWatPmpCntlTimer |
| Hot water comfort pump control timer. More... | |
| state_t | hotWatPmpSchTimer |
| Hot water comfort pump schedule timer. More... | |
| state_t | load250Wcont |
| Controlled load SFC. | |
| volatile int | nologBatCap |
| Control logging inhibit charging battery due to capacity limit. More... | |
| char const | prgNamPure [] |
| The pure program name. More... | |
| char const | prgSVNdat [] |
| The complete SVN date string. More... | |
| char const | prgSVNrev [] |
| The complete SVN revision string. More... | |
Process control for a modestly smart home.
The program's tasks are mainly related to electrical power involving two or three smart meters (three phase 230/400V) and power modules for battery and solar panel / inverter handling. 2017 the development started on a realistic laboratory experiment and soon (mid 2018) spun off to a real life home control set-up with extra comfort and buffer battery functions.
The MQTT topic root "labExp/sweetHome/" as well as the "hometers" in the application were kept so far out of reverence for the very beginnings.
The program runs on a Raspberry with a GUI-less (no graphics) Raspbian lite having an Apache Webserver and a MQTT broker on the same machine. GUI/HMI is featured as Web-Interface (Apache, C CGI) on the (W)LAN.
Revision history
Communication
This program uses a serial half duplex communication with a TTL to RS485 converter to handle two smart meters (max. 7 m away) via Modbus. The RS485 module forwards all GPIO pins it covers. Growatt inverters have Modbus via RS232, hence not sharable and needing an extra ¶ęC to provide a MQTT interface; see growattLink.c.
MQTT protocol [W]LAN is used to attach more remote periphery, like a battery surveillance connected directly to and supplied by the battery clamps. Other MQTT periphery devices are of the shelf remote power relays. An extra Pi with Modbus over V.24 is supervising a Growatt inverter.
Examples for messages to parse: 'Up: 119.6V P: 65.0W L: 7.2W T: 26.5gdC Wd/t: 0.0 / 2316.2kWh' for topic 'labExp/sweetHome/grow01/meas' 'batU 0 : 12.19 V, adc:641' for topic 'labExp/sweetHome/bat/volt' '{"POWER":"ON"}' for topic 'labExp/sweetHome/plug01/RESULT' 'ON' for topic 'labExp/sweetHome/plug01/POWER' '{"POWER":"OFF"}' for topic 'labExp/sweetHome/plug01/RESULT' 'OFF' for topic 'labExp/sweetHome/plug01/POWER'
HTTP is used for GUI (via Apache). HTTPS is not used as all devices are in a guarded private home or laboratory network. Hence the Pi can't get a Let's Encrypt certificate and self signed ones aren't worth the while.
A 1-wire bus is used to add some analogue sensors to the ADC abstinent Raspberry Pi. At present there are three hot water tank/pipe temperature sensors to limit the 0..6kW ballast heater usage.
Common memory is used to communicate with other programs. At present this is a CGI program (meteRead.c) handling the AJAX link to the Apache based Web interface (meteRead.html).
GPIO usage
GPIO pins are used to control relays, LEDs and one button switch in the Pi's immediate neighbourhood. Three open drain outputs in a small extra module control slightly remote (10m) periphery via shielded cable and acts as guard to the virtually non-protected Raspberry IO pins.
The GPIO configuration definitions are found in file sweetHome2.h.
Modbus usage
This program acts as client for two B+G E-Tech three phase EASTRON smart meters, currebtly (2017..25) one SDM630-Modbus and one SDM530-Modbus. (One phase meters like SDM230-Modbus were used in early experimental lab set-ups, only). Even in the Lab, no B+G E-Tech EASTRON Modbus communicated reliably above 9.6 baud (the default). Due to this low speed and, additionally, low response times, only two meter readings per second are possible over one RS485 line.
Communication will be 'RTU / RS485 9600, none' as common denominator. See Eastron, SMD230Modbus, Smart Meter Modbus Protocol Implementation V1.2 Eastron, SDM630Modbus, Smart Meter Modbus Protocol 630 (V1.5 ?) Eastron, SDM530Modbus, Smart Meter Modbus Protocol 530 (V1.1 or V1.5)
Due to the similar register layout, the meter types can be replaced with virtually no change in program. The SMD230, of course, can only be used if one phase (L1) is sufficient. Due to its technical inferiority it is not recommended except if space limitations on the DIN rails are hard.
The usage of said two three phase meters is: A - slave 3: a SDM530Modbus three phase meter for the whole house. L1 L2 L3 are the three phases, each max. 80 A to supply the building under experiment. Import means power flows from public supply. Export is considered prohibited and will be inhibited by controlling suitable consumers, as water tank heater, and the buffer battery. In 2023 a car chargers power control/limit (by PWM) was added. As there is no suitable electric cars for an reasonable this feature is not used, yet. B - slave 0: a SDM630Modbus meter is used for two solar panel groups (A1/mJB, B3/big) and one for waste (ballast) and battery (C3/xLD). Import means power flow from panels respectively battery; export means battery charging and extra (waste/ballast) consumers including idle inverter losses.
One severe complication with serial communication is the frequent change of the serial interface's name with Raspbian version changes.
Timing
The program has a cyclic process control (in SPS manner, like Simatic, e.g.). As of Revision 150 we have
a) a 20 ms cycle for phase packet switching
b) a 100 ms cycle for process control, process I/O, SFCs etc.
c) an 1s cycle for Modbus *) communication, timer handling and some SFCs
The 100 ms cycle b) does not use the the library's (weUtil.c / .h) own generic 100 ms cycle but a by 5 sub-division of the 20 ms cycle a).
The generic cycles (a and c) are, by library implementation, own threads.
) The Eastron meter communication is dead slow by both answering delays and default baud rate. All experiment deviating from default settings failed.
Server functions
This program handles error, log, hibernation etc. files to provide values for humans and other programs.
Additionally shared memory and a set of three semaphores is provided to share real time values with other (C) programs as well as for receiving command and status information. This interface is also used to provide web interfaces in a flexible way.
And the program acts as MQTT subscriber and publisher using a mosquitto broker, currently on the same Raspberry Pi. The primary MQTT purpose was to have "own" MQTT periphery. Those are remote power relays and a battery voltage monitoring ESP8266 in the private LAN – the latter choice was made due to Pi's lack of analogue input.
Library usage
The program uses the standard libraries pthread, pigpiod_if2, modbus, shm, sem and mosquitto. The own libraries in weRasp/..c and include/..h, namely weModbus, weGPIOd, sysUtil, weShareMem, weCGIajax etc. are compiled and linked in.
They could also be converted to and used as .so library. But, in the application environment given, this approach brings no advantage.
Build the program
cross-compile by:
program by:
or due to some bugs in make use winscp and IP directly by:
The latter's correct variant is generated as output of make ... progapp
| void initDawnDusk | ( | void | ) |
Adjust dawn and dusk timers.
This method initialises the settings of the dawn and dusk timers at program (re-) start.
| int readRetFil | ( | void | ) |
Read (day start) retain file.
| int writeRetFil | ( | void | ) |
Write (day start) retain file.
| void initSunRiSet | ( | int | init | ) |
Initialise dawn, sunrise, sunset and dusk timers.
The function is currently used only at program start start.
If the parameter init is true valFilVal .dayStrtVal values will be calculated for the current day. Otherwise the are assumed to be valid, i.e. read usually from that days start (hibernation) file. initDawnDusk() will be called in any case.
| init | true: calculate dayStrtVal else use file |
| int writeDayFil | ( | void | ) |
Append daily entry work table.
See dayFilNam
| void hotWatPmpCntlTimChg | ( | state_t *const | me | ) |
Hot water comfort pump control timer state change function.
It turns the pump on respectively off. At turning off the next on schedule is determined and set as next end time for the "hot water comfort pump schedule timer".
| me | pointer to the pump timer |
| void hotWpmpButChg | ( | state_t *const | me | ) |
Hot water comfort pump button state change.
This function (re-) triggers the hot water comfort pump timer.
| void hotWatPmpSchTimChg | ( | state_t *const | me | ) |
Hot water comfort pump schedule timer state change function.
It starts pump control timer.
| me | pointer to the pump timer |
| void batUnloadStatChg | ( | state_t *const | me | ) |
Battery unload SFC state changes.
This sequential state machine (SFC) organises the battery unload via a step up converter module and one input of a low voltage (60V) two input solar inverter for a small panel set.
It handles all switching steps and (inhibit) conditions. Used in:
state_t batUnloadSeq = newSeqCont("batUnloadSeq", batUnloadStatChg,
| me | pointer to the battery unload SFC, never NULL (not checked!) |
| void batCntBalStatChg | ( | state_t *const | me | ) |
Battery as controlled ballast SFC state changes.
This sequential state machine organises the battery load as ballast for surplus energy.
| me | pointer to the battery ballast load SFC, never NULL (not checked) |
| void batCntLodStatChg | ( | state_t *const | me | ) |
Battery controlled load or keep; SFC state changes.
This sequential state machine organises the battery loading and keeping it in good load state. Not to be confused with batCntBalStatChg, i.e. using battery as storage/ballast.
| me | pointer to the battery unload SFC, never NULL |
| void load250WstateChg | ( | state_t *const | me | ) |
Controlled fixed surplus consumption; SFC state changes.
This sequential state machine organises the the handling of a load of about 250 W with restricted switching rules. One example is a compressor air drier.
The rules implemented here are:
When switched ON, run at least 30 minutes before allow turning OFF.
Do not turn ON twice within 2 hours.
Do not run longer than 4 hours.
The controlled load is at Plg1 (MQTT). The additional relay 5's (8 relays module directly at Pi) contact is currently not used. Its LED still serves as a signal of Plg1's state.
| me | pointer to the controlled load (0) SFC, never NULL |
| void switchARN4105 | ( | uint8_t const | cutOff | ) |
AR-N 4105 implementation function.
This is the VDE-AR-N 4105 cut off function to be provided site specific by user / application software. It does the cut off respectively switch back on the generators and optionally log the event.
| cutOff | not 0: do the cut off, 0: switch generators back on |
| void * processIOthread | ( | void * | args | ) |
The task of controlling process IO.
With the exception of the Modbus / RS485 coupled smart meters, this thread controls almost all processIO: 8 relay outputs, some LEDs, one button input, one PWM signal and some open drain M-switches.
It is a 20 ms cycle thread to control line power by phase packet switching while all the rest is effectively done in a 100 ms cycle, i.e. every 5th 20ms cycle.
The cyclic 100 ms task then distinguishes 10 steps within a second and sometimes differentiates odd and even seconds by the actual Modbus slave number in the respective 1s RS4885 thread.
This ten (0..9) respectively twenty steps serve the synchronisation with the other thread and act as state machine framework.
1, 6: command processing; 7: wouldGive250WHyst
| int main | ( | int | argc, |
| char ** | argv | ||
| ) |
The program.
run by: hometersControl [options
For options see longOptions and :: optHlpTxt.
| char const prgNamPure[] |
The pure program name.
To be provided in the application's / program's source.
| char const prgSVNrev[] |
The complete SVN revision string.
To be provided in the application's / program's source.
| char const prgSVNdat[] |
The complete SVN date string.
To be provided in the application's / program's source.
| volatile int nologBatCap |
Control logging inhibit charging battery due to capacity limit.
1: log next time when it occurs and (always) decrement 0: or high value log no more this day. n: inhibit the next 2*n seconds while continually trying
| volatile off_t dayFileSize |
The size of the work table before last/actual daily entry.
This is the previous size of the file dayFilNam or negative in case of errors.
| const char* dayFileHeader |
CSV header of the file dayFilNam.
time xsl (column 1) is an absolute time in Excel / Libre Office Calc format. It is a 64 bit IEEE 754 floating point, i.e. C double, value calculated from the UTC 32 bit int time used here by
The same holds for the four dawn to dusk times, columns 10..13. The time zone in our case is CET (MEZ) or CEST (MESZ) which a German Excel or Calc should know to display the time/date format chosen correctly. To control this column 2 "+ /h" is an integer telling the offset in hours to UTC. It would be +1 for normal Central European time and +2 in case of summer time.
| const char* dayFileFormat |
Format string for the file dayFilNam.
| __time_t const hotTimSched[] |
Schedule times for hot water comfort pump starts.
This is a sorted array starting with the earliest start time in seconds relative to localMidnight. At least the first (earliest) entry has to be put at the end 24h (86400s) later, to find a next day entry.
Note: The first entry itself is nevertheless not dispensible as the program may be started between midnight and that time.
Note 2: At days with DST change the first time in that day will be by one hour wrong. Hence, beware using this approach for other purposes without consideration.
Note 3: __time_t (= long int = 64 bit) could be replaced by int (32bit) as it is not used for absolute epoch times. The entries are added to such values, like localMidnight.
Hint 4: This sorted array of a day's clock readings might be replaced by an array of structures to implement day of week as filter condition for a schedule time.
| state_t hotWatPmpSchTimer |
Hot water comfort pump schedule timer.
This timer is running to the next time to start the comfort pump on schedule. On running out it will trigger the pump control timer to start the pump or prolong its run time accordingly. Afterwards this timer will be restarted to the next schedule point in future.
| state_t hotWatPmpCntlTimer |
Hot water comfort pump control timer.
This timer is running / active as long as the comfort pump is running. It might be triggered or prolonged by push button (hotWpmpButChg), web interface command or the pump schedule timer.