adding project files
current status: power and energy measuring works (not very well in (0;10W) range) not well defined behavior on network/mqtt disconnects current and voltage measurements TBDmaster
commit
6489e78cdc
|
@ -0,0 +1,13 @@
|
|||
*.d
|
||||
*.o
|
||||
*.elf
|
||||
*.hex
|
||||
*.map
|
||||
*.swp
|
||||
*.swo
|
||||
*.*.sw*
|
||||
*.obj
|
||||
*.gdb_history
|
||||
*__pycache__*
|
||||
**/debug/
|
||||
**/build
|
|
@ -0,0 +1,45 @@
|
|||
spejsiot Sonoff® POW
|
||||
====================
|
||||
"Spejsiot Building Automation Framework" implementation of Sonoff® POW
|
||||
|
||||
dependencies
|
||||
------------
|
||||
copy/link those dependencies to following directories:
|
||||
|
||||
* [esp-open-sdk](https://github.com/pfalcon/esp-open-sdk) to ext/esp/esp-opensdk
|
||||
* [Sming](https://github.com/SmingHub/Sming) to ext/esp/Sming
|
||||
* [rboot](https://github.com/raburton/rboot) to ext/esp/rboot
|
||||
|
||||
preparation
|
||||
-----------
|
||||
$ source cfg/env_noproject
|
||||
|
||||
or if you are feeling brave:
|
||||
|
||||
$ ./init.sh
|
||||
|
||||
credits
|
||||
-------
|
||||
created by: Jan Wiśniewski <vuko@hackerspace.pl>
|
||||
|
||||
This project uses [spejsiot](https://wiki.hackerspace.pl/projects:spejsiot).
|
||||
|
||||
|
||||
licence
|
||||
-------
|
||||
All code except for spejsiot part is licensed under _zlib license_:
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
source "${PROJECT_DIR}/cfg/env_spacenode"
|
||||
alias pd="cd $(realpath ${PROJECT_DIR})"
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
export ESP_HOME=$(realpath ${PROJECT_DIR}/ext/esp/esp-open-sdk/)
|
||||
export SMING_HOME=$(realpath ${PROJECT_DIR}/ext/esp/Sming/Sming/)
|
||||
export PATH="$(realpath ${PROJECT_DIR}/ext/esp/esp-open-sdk/esptool2):${PATH}"
|
||||
export PATH="$(realpath ${PROJECT_DIR}/ext/esp/esp-open-sdk/xtensa-lx106-elf/bin/):${PATH}"
|
||||
|
||||
# Make flashing slightly (4x) faster.
|
||||
export ESPBAUD=460800
|
||||
export COM_SPEED_ESPTOOL="$ESPBAUD"
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/bash
|
||||
export PROJECT_DIR=$(realpath ./)
|
||||
source cfg/env
|
|
@ -0,0 +1,45 @@
|
|||
sonoff_pow="$PWD" CD=. filter="*.c *.h *.py" {
|
||||
init.sh
|
||||
README.mk
|
||||
doc/hw.txt
|
||||
doc/cse7759.py
|
||||
|
||||
cfg/env
|
||||
cfg/env_spacenode
|
||||
cfg/env_noproject
|
||||
cfg/vim
|
||||
cfg/vimproject
|
||||
|
||||
soft="soft" CD=. {
|
||||
CMakeLists.txt
|
||||
|
||||
Makefile
|
||||
platform/standalone.rom.ld
|
||||
platform/common.ld
|
||||
platform/rboot.rom0.ld
|
||||
|
||||
src="src" {
|
||||
TcpPublish.cpp
|
||||
TcpPublish.h
|
||||
application.cpp
|
||||
common/common_config.h
|
||||
common/user_config.h
|
||||
impuls_counter.c
|
||||
impuls_counter.h
|
||||
sonoff_pow.cpp
|
||||
sonoff_pow.h
|
||||
spejsiot="spejsiot" {
|
||||
SpejsNode.h
|
||||
Endpoint.h
|
||||
Endpoint.cpp
|
||||
endpoints
|
||||
endpoints/OutputEndpoint.cpp
|
||||
endpoints/OutputEndpoint.h
|
||||
endpoints/ImplementationEndpoint.cpp
|
||||
endpoints/ImplementationEndpoint.h
|
||||
endpoints/ImplementationEndpoint.h
|
||||
SpejsNode.cpp
|
||||
ver.h
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/python3
|
||||
import math
|
||||
|
||||
f_osc = 3.549e+6
|
||||
v_ref = 2.43
|
||||
|
||||
r_i = 1e-3
|
||||
|
||||
x_c = 1 / (2 * math.pi * 50 * 100e-9) # excess
|
||||
r1 = 5*470e+3
|
||||
r2 = 1/(1/1e+3 + 1/x_c)
|
||||
v_2 = r2 / (r1 + r2) * 230
|
||||
|
||||
f_cf_max = v_2 * (16*r_i) * 48 / (v_ref**2) * f_osc / 128
|
||||
f_cfi_max = (16*r_i) * 24 / v_ref * f_osc / 512
|
||||
f_cfv_max = v_2 * 2 / v_ref * f_osc / 512
|
||||
|
||||
joul_to_period = 128 * (v_ref**2) * (r1+r2)/r2 / r_i / (48 * f_osc)
|
||||
joul_to_input_change = joul_to_period/2
|
||||
wh_per_input_change = joul_to_input_change / (60 * 60)
|
||||
w_per_changes_per_second = joul_to_input_change
|
||||
samples_per_second = 4000
|
||||
w_per_changes_per_sample = joul_to_input_change * 4000
|
||||
print("power_factor: {:0.15f}".format(w_per_changes_per_sample))
|
||||
print("energy_factor: {:0.15f}".format(wh_per_input_change))
|
||||
|
||||
f_max = max([f_cf_max, f_cfi_max, f_cfv_max])
|
||||
|
||||
f_sample_min = f_max * 2 * 1.2
|
||||
t_sample = 1/f_sample_min
|
|
@ -0,0 +1,22 @@
|
|||
sonoff ver 2.0
|
||||
esp to cse7795
|
||||
(12 - GPIO13 - D7) pin esp - CF1
|
||||
( 9 - GPIO14 - D5) pin esp - CF
|
||||
(24 - GPIO5 - D1) pin esp - SEL
|
||||
esp to relay
|
||||
(10 - GPIO12 - D6) pin esp - relay transistor and LED
|
||||
flash memory: winbond 25q32fvsig 32M(i)b (4MiB)
|
||||
|
||||
uart pinout
|
||||
1 GND
|
||||
2 TXD (esp 26 pin)
|
||||
3 RXD (esp 25 pin)
|
||||
4 VCC
|
||||
|
||||
programing sequence:
|
||||
1. make sure device *is disconnected* from mains
|
||||
2. if device is connected to maing go to point 1
|
||||
3. hold sonoff button
|
||||
4. power device from VCC pin (3.3V)
|
||||
5. release sonoff button
|
||||
6. make flash
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash
|
||||
BASEDIR=$(dirname ${BASH_SOURCE})
|
||||
cd "${BASEDIR}"
|
||||
PROJECT_NAME=${PWD##*/}
|
||||
|
||||
tmux attach-session -t "${PROJECT_NAME}" && exit 0
|
||||
|
||||
PROJECT_DIR="$PWD"
|
||||
ENV="$(realpath ./cfg/env)"
|
||||
tmux new-session -d -P -s "${PROJECT_NAME}" cat || exit 1
|
||||
tmux set-environment -t "${PROJECT_NAME}" "ENV" "${ENV}"
|
||||
tmux set-environment -t "${PROJECT_NAME}" "BASH_ENV" "${ENV}"
|
||||
tmux set-environment -t "${PROJECT_NAME}" "BASH_ENV_INT" "${ENV}"
|
||||
tmux set-environment -t "${PROJECT_NAME}" "PROJECT_NAME" "${PROJECT_NAME}"
|
||||
tmux set-environment -t "${PROJECT_NAME}" "PROJECT_DIR" "${PROJECT_DIR}"
|
||||
|
||||
tmux new-window -t "${PROJECT_NAME}:1" cat
|
||||
tmux kill-window -t "${PROJECT_NAME}:0"
|
||||
tmux new-window -t "${PROJECT_NAME}:0" "bash -i -c \"NVIM_TUI_ENABLE_TRUE_COLOR=1 nvim -S cfg/vim\""
|
||||
tmux split-window -t "${PROJECT_NAME}:0" -c "${PROJECT_DIR}/soft/"
|
||||
tmux resize-pane -t "${PROJECT_NAME}:0.0" -Z
|
||||
tmux kill-window -t "${PROJECT_NAME}:1"
|
||||
tmux set-option -t "${PROJECT_NAME}" allow-rename off
|
||||
tmux set-window-option -t "${PROJECT_NAME}" aggressive-resize on
|
||||
tmux rename-window -t "${PROJECT_NAME}:0" vim
|
||||
tmux attach-session -t "${PROJECT_NAME}"
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
cmake_minimum_required (VERSION 3.0)
|
||||
set(CMAKE_SYSTEM_NAME Generic)
|
||||
set(CMAKE_SYSTEM_VERSION 1)
|
||||
|
||||
project (comm_mcu)
|
||||
|
||||
# specify the cross compiler
|
||||
set(GCC_PREFIX "xtensa-lx106-elf")
|
||||
set(CMAKE_C_COMPILER "${GCC_PREFIX}-gcc")
|
||||
set(CMAKE_CXX_COMPILER "${GCC_PREFIX}-g++")
|
||||
|
||||
set(CPP_FLAGS "${CPP_FLAGS} -Os -g")
|
||||
|
||||
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wundef -Wno-unused-function -Wcast-align -Wextra -Wshadow -Wimplicit-function-declaration -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes")
|
||||
|
||||
set(CPP_FLAGS "${CPP_FLAGS} -Wpointer-arith -Wundef -Werror -Wl,-EL -nostdlib -mlongcalls -finline-functions -mtext-section-literals")
|
||||
set(CPP_FLAGS "${CPP_FLAGS} -D__ets__ -DICACHE_FLASH -DARDUINO=106 -DCOM_SPEED_SERIAL=115200 -DENABLE_CMD_EXECUTOR=1 -DCUST_FILE_BASE=application -DDEBUG_VERBOSE_LEVEL=2 -DDEBUG_PRINT_FILENAME_AND_LINE=0 -DDISABLE_SPIFFS=1 -DBOOT_RTC_ENABLED=1")
|
||||
set(CPP_FLAGS "${CPP_FLAGS} -DRBOOT_SPIFFS_0=0x100000 -DRBOOT_SPIFFS_1=0x300000 -DRBOOT_INTEGRATION -DBOOT_BIG_FLASH -DDISABLE_SPIFFS=1 -DSPIFF_SIZE=196608")
|
||||
|
||||
# disable exceptions and Run-Time Type Information
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -felide-constructors")
|
||||
|
||||
# remove unused objects
|
||||
set(CPP_FLAGS "${CPP_FLAGS} -ffunction-sections -fdata-sections")
|
||||
|
||||
|
||||
# CPP FLAGS
|
||||
set(CMAKE_C_FLAGS "${CPP_FLAGS} ${CMAKE_C_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS "${CPP_FLAGS} ${CMAKE_CXX_FLAGS}")
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L.")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Trboot.rom0.ld -nostdlib -u Cache_Read_Enable_New -u spiffs_get_storage_config -u call_user_start -Wl,--gc-sections -Wl,-wrap,system_restart_local")
|
||||
get_filename_component(EXT_PATH "../../ext/" ABSOLUTE)
|
||||
|
||||
get_filename_component(PLATFORM_PATH "platform/" ABSOLUTE)
|
||||
link_directories(${PLATFORM_PATH})
|
||||
|
||||
# esp-open-sdk
|
||||
get_filename_component(SDK_PATH "${EXT_PATH}/esp/esp-open-sdk" ABSOLUTE)
|
||||
include_directories("${SDK_PATH}/include/")
|
||||
include_directories("${SDK_PATH}/sdk/include/")
|
||||
link_directories("${SDK_PATH}/lib/")
|
||||
link_directories("${SDK_PATH}/sdk/ld")
|
||||
set(BLANK_BIN "${SDK_PATH}/sdk/bin/blank.bin")
|
||||
set(ESPTOOL "${SDK_PATH}/esptool/esptool.py")
|
||||
set(TLIBS ${TLIBS} wpa pp phy smartconfig net80211 crypto hal)
|
||||
|
||||
# rboot
|
||||
get_filename_component(RBOOT_PATH "${EXT_PATH}/esp/rboot" ABSOLUTE)
|
||||
include_directories("${RBOOT_PATH}/")
|
||||
include_directories("${RBOOT_PATH}/appcode/")
|
||||
link_directories("${RBOOT_PATH}/main/lib")
|
||||
set(RBOOT_BIN "${RBOOT_PATH}/firmware/rboot.bin")
|
||||
set(TLIBS ${TLIBS} main2 rboot)
|
||||
|
||||
# Sming
|
||||
get_filename_component(SMING_PATH "${EXT_PATH}/esp/Sming/Sming" ABSOLUTE)
|
||||
include_directories("${SMING_PATH}/include/")
|
||||
include_directories("${SMING_PATH}/")
|
||||
include_directories("${SMING_PATH}/third-party/esp-open-lwip/include")
|
||||
include_directories("${SMING_PATH}/system/include")
|
||||
include_directories("${SMING_PATH}/Wiring")
|
||||
include_directories("${SMING_PATH}/Libraries")
|
||||
include_directories("${SMING_PATH}/Libraries/Adafruit_GFX")
|
||||
include_directories("${SMING_PATH}/SmingCore")
|
||||
include_directories("${SMING_PATH}/Services/SpifFS")
|
||||
include_directories("${SMING_PATH}/third-party/http-parser")
|
||||
include_directories("${SMING_PATH}/third-party/spiffs/src")
|
||||
link_directories("${SMING_PATH}/compiler/lib/") # Sming libraries
|
||||
set(TLIBS ${TLIBS} sming microc microgcc lwip_open pwm_open)
|
||||
|
||||
add_library (rboot STATIC
|
||||
${RBOOT_PATH}/appcode/rboot-api.c
|
||||
${RBOOT_PATH}/appcode/rboot-bigflash.c
|
||||
${SMING_PATH}/appspecific/rboot/overrides.c
|
||||
)
|
||||
|
||||
|
||||
include_directories("src")
|
||||
include_directories("src/spejsiot")
|
||||
include_directories("src/spejsiot/include")
|
||||
include_directories("src/common")
|
||||
|
||||
add_library (app STATIC
|
||||
src/application.cpp
|
||||
|
||||
src/sonoff_pow.cpp
|
||||
src/impuls_counter.c
|
||||
|
||||
src/spejsiot/Endpoint.cpp
|
||||
src/spejsiot/endpoints/OutputEndpoint.cpp
|
||||
src/spejsiot/endpoints/ImplementationEndpoint.cpp
|
||||
src/spejsiot/SpejsNode.cpp
|
||||
src/TcpPublish.cpp
|
||||
${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ver.c
|
||||
)
|
||||
|
||||
add_executable(main.elf
|
||||
${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/empty.c)
|
||||
|
||||
target_link_libraries(main.elf -Wl,--start-group app ${TLIBS} -Wl,--end-group)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/ver.c
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
|
||||
COMMAND echo \"char* BUILD_ID = \\\"$$\(git rev-parse --short HEAD\)-$$\(TZ=UTC date +%Y%m%d-%H%M%S\)\\\"\;\" > ver.c
|
||||
)
|
||||
|
||||
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/empty.c
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}
|
||||
COMMAND touch empty.c
|
||||
)
|
||||
|
||||
add_custom_command(OUTPUT firmware/rom0.bin
|
||||
COMMAND mkdir -p firmware/
|
||||
COMMAND esptool2 -quiet -bin -boot2 main.elf firmware/rom0.bin .text .data .rodata
|
||||
DEPENDS main.elf)
|
||||
|
||||
add_custom_target(flash
|
||||
DEPENDS firmware/rom0.bin
|
||||
DEPENDS ${RBOOT_BIN}
|
||||
COMMAND python2 "${ESPTOOL}" -p /dev/ttyUSB0 -b 460800 write_flash -ff 40m -fm qio -fs 4MB 0x00000 ${RBOOT_BIN} 0x01000 ${BLANK_BIN} 0x02000 firmware/rom0.bin)
|
|
@ -0,0 +1,12 @@
|
|||
all:
|
||||
mkdir -p build
|
||||
cd build; cmake ..
|
||||
cd build; make --no-print-directory
|
||||
|
||||
flash:
|
||||
mkdir -p build
|
||||
cd build; cmake ..
|
||||
@- cd build; make --no-print-directory flash
|
||||
|
||||
clean:
|
||||
rm -r build
|
|
@ -0,0 +1,242 @@
|
|||
/*
|
||||
common.ld
|
||||
*/
|
||||
|
||||
PHDRS
|
||||
{
|
||||
dport0_0_phdr PT_LOAD;
|
||||
dram0_0_phdr PT_LOAD;
|
||||
dram0_0_bss_phdr PT_LOAD;
|
||||
iram1_0_phdr PT_LOAD;
|
||||
irom0_0_phdr PT_LOAD;
|
||||
}
|
||||
|
||||
|
||||
/* Default entry point: */
|
||||
ENTRY(call_user_start)
|
||||
EXTERN(_DebugExceptionVector)
|
||||
EXTERN(_DoubleExceptionVector)
|
||||
EXTERN(_KernelExceptionVector)
|
||||
EXTERN(_NMIExceptionVector)
|
||||
EXTERN(_UserExceptionVector)
|
||||
PROVIDE(_memmap_vecbase_reset = 0x40000000);
|
||||
/* Various memory-map dependent cache attribute settings: */
|
||||
_memmap_cacheattr_wb_base = 0x00000110;
|
||||
_memmap_cacheattr_wt_base = 0x00000110;
|
||||
_memmap_cacheattr_bp_base = 0x00000220;
|
||||
_memmap_cacheattr_unused_mask = 0xFFFFF00F;
|
||||
_memmap_cacheattr_wb_trapnull = 0x2222211F;
|
||||
_memmap_cacheattr_wba_trapnull = 0x2222211F;
|
||||
_memmap_cacheattr_wbna_trapnull = 0x2222211F;
|
||||
_memmap_cacheattr_wt_trapnull = 0x2222211F;
|
||||
_memmap_cacheattr_bp_trapnull = 0x2222222F;
|
||||
_memmap_cacheattr_wb_strict = 0xFFFFF11F;
|
||||
_memmap_cacheattr_wt_strict = 0xFFFFF11F;
|
||||
_memmap_cacheattr_bp_strict = 0xFFFFF22F;
|
||||
_memmap_cacheattr_wb_allvalid = 0x22222112;
|
||||
_memmap_cacheattr_wt_allvalid = 0x22222112;
|
||||
_memmap_cacheattr_bp_allvalid = 0x22222222;
|
||||
PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
|
||||
.dport0.rodata : ALIGN(4)
|
||||
{
|
||||
_dport0_rodata_start = ABSOLUTE(.);
|
||||
*(.dport0.rodata)
|
||||
*(.dport.rodata)
|
||||
_dport0_rodata_end = ABSOLUTE(.);
|
||||
} >dport0_0_seg :dport0_0_phdr
|
||||
|
||||
.dport0.literal : ALIGN(4)
|
||||
{
|
||||
_dport0_literal_start = ABSOLUTE(.);
|
||||
*(.dport0.literal)
|
||||
*(.dport.literal)
|
||||
_dport0_literal_end = ABSOLUTE(.);
|
||||
} >dport0_0_seg :dport0_0_phdr
|
||||
|
||||
.dport0.data : ALIGN(4)
|
||||
{
|
||||
_dport0_data_start = ABSOLUTE(.);
|
||||
*(.dport0.data)
|
||||
*(.dport.data)
|
||||
_dport0_data_end = ABSOLUTE(.);
|
||||
} >dport0_0_seg :dport0_0_phdr
|
||||
|
||||
.data : ALIGN(4)
|
||||
{
|
||||
_data_start = ABSOLUTE(.);
|
||||
*(.data)
|
||||
*(.data.*)
|
||||
*(.gnu.linkonce.d.*)
|
||||
*(.data1)
|
||||
*(.sdata)
|
||||
*(.sdata.*)
|
||||
*(.gnu.linkonce.s.*)
|
||||
*(.sdata2)
|
||||
*(.sdata2.*)
|
||||
*(.gnu.linkonce.s2.*)
|
||||
*(.jcr)
|
||||
_data_end = ABSOLUTE(.);
|
||||
} >dram0_0_seg :dram0_0_phdr
|
||||
|
||||
.rodata : ALIGN(4)
|
||||
{
|
||||
_rodata_start = ABSOLUTE(.);
|
||||
*(.sdk.version)
|
||||
*(.rodata)
|
||||
*(.rodata.*)
|
||||
*(.gnu.linkonce.r.*)
|
||||
*(.rodata1)
|
||||
__XT_EXCEPTION_TABLE__ = ABSOLUTE(.);
|
||||
*(.xt_except_table)
|
||||
*(.gcc_except_table)
|
||||
*(.gnu.linkonce.e.*)
|
||||
*(.gnu.version_r)
|
||||
*(.eh_frame)
|
||||
. = (. + 3) & ~ 3;
|
||||
/* C++ constructor and destructor tables, properly ordered: */
|
||||
__dso_handle = ABSOLUTE(.);
|
||||
__init_array_start = ABSOLUTE(.);
|
||||
KEEP (*crtbegin.o(.ctors))
|
||||
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
|
||||
KEEP (*(SORT(.ctors.*)))
|
||||
KEEP (*(.ctors))
|
||||
__init_array_end = ABSOLUTE(.);
|
||||
KEEP (*crtbegin.o(.dtors))
|
||||
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
|
||||
KEEP (*(SORT(.dtors.*)))
|
||||
KEEP (*(.dtors))
|
||||
/* C++ exception handlers table: */
|
||||
__XT_EXCEPTION_DESCS__ = ABSOLUTE(.);
|
||||
*(.xt_except_desc)
|
||||
*(.gnu.linkonce.h.*)
|
||||
__XT_EXCEPTION_DESCS_END__ = ABSOLUTE(.);
|
||||
*(.xt_except_desc_end)
|
||||
*(.dynamic)
|
||||
*(.gnu.version_d)
|
||||
. = ALIGN(4); /* this table MUST be 4-byte aligned */
|
||||
_bss_table_start = ABSOLUTE(.);
|
||||
LONG(_bss_start)
|
||||
LONG(_bss_end)
|
||||
_bss_table_end = ABSOLUTE(.);
|
||||
_rodata_end = ABSOLUTE(.);
|
||||
} >dram0_0_seg :dram0_0_phdr
|
||||
|
||||
.bss ALIGN(8) (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
. = ALIGN (8);
|
||||
_bss_start = ABSOLUTE(.);
|
||||
*(.dynsbss)
|
||||
*(.sbss)
|
||||
*(.sbss.*)
|
||||
*(.gnu.linkonce.sb.*)
|
||||
*(.scommon)
|
||||
*(.sbss2)
|
||||
*(.sbss2.*)
|
||||
*(.gnu.linkonce.sb2.*)
|
||||
*(.dynbss)
|
||||
*(.bss)
|
||||
*(.bss.*)
|
||||
*(.gnu.linkonce.b.*)
|
||||
*(COMMON)
|
||||
. = ALIGN (8);
|
||||
_bss_end = ABSOLUTE(.);
|
||||
_heap_start = ABSOLUTE(.);
|
||||
/* _stack_sentry = ALIGN(0x8); */
|
||||
} >dram0_0_seg :dram0_0_bss_phdr
|
||||
/* __stack = 0x3ffc8000; */
|
||||
|
||||
.irom0.text : ALIGN(4)
|
||||
{
|
||||
_irom0_text_start = ABSOLUTE(.);
|
||||
|
||||
*libsmartconfig.a:(.literal .text .literal.* .text.*)
|
||||
*libstdc++.a:(.literal .text .literal.* .text.*)
|
||||
*liblwip_open.a:(.literal .text .literal.* .text.*)
|
||||
*liblwip_full.a:(.literal .text .literal.* .text.*)
|
||||
*liblwip2.a:(.literal .text .literal.* .text.*)
|
||||
*libaxtls.a:(.literal .text .literal.* .text.*)
|
||||
*libat.a:(.literal.* .text.*)
|
||||
*libcrypto.a:(.literal.* .text.*)
|
||||
*libespnow.a:(.literal.* .text.*)
|
||||
*libjson.a:(.literal.* .text.*)
|
||||
*liblwip.a:(.literal.* .text.*)
|
||||
*libmesh.a:(.literal.* .text.*)
|
||||
*libnet80211.a:(.literal.* .text.*)
|
||||
*libsmartconfig.a:(.literal.* .text.*)
|
||||
*libssl.a:(.literal.* .text.*)
|
||||
*libupgrade.a:(.literal.* .text.*)
|
||||
*libwpa.a:(.literal.* .text.*)
|
||||
*libwpa2.a:(.literal.* .text.*)
|
||||
*libwps.a:(.literal.* .text.*)
|
||||
|
||||
*libmbedtls.a:(.literal.* .text.*)
|
||||
|
||||
*libm.a:(.literal .text .literal.* .text.*)
|
||||
|
||||
*(.irom0.literal .irom.literal .irom.text.literal .irom0.text .irom.text .irom.debug.*)
|
||||
*libapp.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.* .irom.debug.*)
|
||||
*librboot.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.* .irom.debug.*)
|
||||
*libcapnp.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.* .irom.debug.*)
|
||||
*libsming.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.* .irom.debug.*)
|
||||
*libsmingssl.a:*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.* .irom.debug.*)
|
||||
|
||||
_irom0_text_end = ABSOLUTE(.);
|
||||
_flash_code_end = ABSOLUTE(.);
|
||||
} >irom0_0_seg :irom0_0_phdr
|
||||
|
||||
.text : ALIGN(4)
|
||||
{
|
||||
_stext = .;
|
||||
_text_start = ABSOLUTE(.);
|
||||
*(.UserEnter.text)
|
||||
. = ALIGN(16);
|
||||
*(.DebugExceptionVector.text)
|
||||
. = ALIGN(16);
|
||||
*(.NMIExceptionVector.text)
|
||||
. = ALIGN(16);
|
||||
*(.KernelExceptionVector.text)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
. = ALIGN(16);
|
||||
*(.UserExceptionVector.text)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
. = ALIGN(16);
|
||||
*(.DoubleExceptionVector.text)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
LONG(0)
|
||||
. = ALIGN (16);
|
||||
*(.entry.text)
|
||||
*(.init.literal)
|
||||
*(.init)
|
||||
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
||||
*(.iram.literal .iram.text.literal .iram.text)
|
||||
*(.fini.literal)
|
||||
*(.fini)
|
||||
*(.gnu.version)
|
||||
_text_end = ABSOLUTE(.);
|
||||
_etext = .;
|
||||
} >iram1_0_seg :iram1_0_phdr
|
||||
|
||||
.lit4 : ALIGN(4)
|
||||
{
|
||||
_lit4_start = ABSOLUTE(.);
|
||||
*(*.lit4)
|
||||
*(.lit4.*)
|
||||
*(.gnu.linkonce.lit4.*)
|
||||
_lit4_end = ABSOLUTE(.);
|
||||
} >iram1_0_seg :iram1_0_phdr
|
||||
}
|
||||
|
||||
/* get ROM code address */
|
||||
INCLUDE "../ld/eagle.rom.addr.v6.ld"
|
|
@ -0,0 +1,11 @@
|
|||
/* Linker Script for rboot */
|
||||
|
||||
MEMORY
|
||||
{
|
||||
dport0_0_seg : org = 0x3FF00000, len = 0x10
|
||||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
|
||||
iram1_0_seg : org = 0x40100000, len = 0x8000
|
||||
irom0_0_seg : org = 0x40202010, len = (1M - 0x2010)
|
||||
}
|
||||
|
||||
INCLUDE "common.ld"
|
|
@ -0,0 +1,11 @@
|
|||
/* linker script for espressif bootloader */
|
||||
|
||||
MEMORY
|
||||
{
|
||||
dport0_0_seg : org = 0x3FF00000, len = 0x10
|
||||
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
|
||||
iram1_0_seg : org = 0x40100000, len = 0x8000
|
||||
irom0_0_seg : org = 0x4020a000, len = (1M - 0x0a000)
|
||||
}
|
||||
|
||||
INCLUDE "common.ld"
|
|
@ -0,0 +1,28 @@
|
|||
#include <SmingCore/SmingCore.h>
|
||||
#include <TcpPublish.h>
|
||||
#include <stdint.h>
|
||||
|
||||
TcpPublish::TcpPublish() : TcpServer() {
|
||||
};
|
||||
|
||||
int TcpPublish::write(const char * str, int len) {
|
||||
int i = 0;
|
||||
for ( i=0; i < clients.count(); i++ ) {
|
||||
clients[i]->write(str, len);
|
||||
}
|
||||
return i;
|
||||
};
|
||||
|
||||
void TcpPublish::onClient(TcpClient* client) {
|
||||
clients.addElement(client);
|
||||
};
|
||||
|
||||
void TcpPublish::onClientComplete(TcpClient* client) {
|
||||
for ( int i=0; i < clients.count(); i++ ) {
|
||||
if(client == clients[i]){
|
||||
clients.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#include <stdint.h>
|
||||
|
||||
class TcpPublish: public TcpServer {
|
||||
public:
|
||||
TcpPublish();
|
||||
int write(const char * str, int len);
|
||||
Vector<TcpClient*> clients;
|
||||
|
||||
protected:
|
||||
void onClient(TcpClient* client);
|
||||
void onClientComplete(TcpClient* client);
|
||||
};
|
|
@ -0,0 +1,74 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include <SmingCore/SmingCore.h>
|
||||
#include <SmingCore/HardwareTimer.h>
|
||||
|
||||
#include <SpejsNode.h>
|
||||
#include <Endpoint.h>
|
||||
#include <endpoints/OutputEndpoint.h>
|
||||
#include <TcpPublish.h>
|
||||
|
||||
extern "C" {
|
||||
#include "sonoff_pow.h"
|
||||
};
|
||||
|
||||
SpejsNode node("sonoff pow");
|
||||
OutputEndpoint *relay;
|
||||
TcpPublish tcpPublish;
|
||||
|
||||
Hardware_Timer cseMeasureTimer;
|
||||
Timer cseUpdateTimer;
|
||||
Timer printTimer;
|
||||
bool started;
|
||||
|
||||
void startServers() {
|
||||
tcpPublish.listen(123);
|
||||
// TODO change to false when disconnected
|
||||
started = true;
|
||||
}
|
||||
|
||||
void print() {
|
||||
char buf[30];
|
||||
memset(buf, 0, 30);
|
||||
|
||||
int len = 0;
|
||||
float power_factor = 21517.217583641264;
|
||||
float power =
|
||||
(float)cse.power.changes * power_factor / (float)cse.power.samples;
|
||||
double energy_factor = 0.001494251221086199;
|
||||
double energy = (double)cse.energy * energy_factor;
|
||||
len = m_snprintf(buf, 30, "%.2f W\n", power);
|
||||
tcpPublish.write("power: ", 7);
|
||||
tcpPublish.write(buf, len);
|
||||
if ( started == true ) {
|
||||
relay->notify("power", String(buf, len));
|
||||
}
|
||||
|
||||
len = m_snprintf(buf, 30, "%.4f Wh\n", energy);
|
||||
tcpPublish.write("energy: ", 8);
|
||||
tcpPublish.write(buf, len);
|
||||
if ( started == true ) {
|
||||
// TODO ignore notify if not connected to mqtt broker
|
||||
relay->notify("energy", String(buf, len));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void init() {
|
||||
Serial.begin(SERIAL_BAUD_RATE); // 115200 by default
|
||||
Serial.systemDebugOutput(true);
|
||||
|
||||
relay = new OutputEndpoint(RELAY_PIN);
|
||||
node.registerEndpoint("relay", relay);
|
||||
node.onConnectedCallback = startServers;
|
||||
node.init();
|
||||
|
||||
cse_init( 4000 );
|
||||
cseMeasureTimer.initializeUs(250, cse_measure_isr);
|
||||
cseMeasureTimer.start();
|
||||
cseUpdateTimer.initializeMs(100, cse_update);
|
||||
cseUpdateTimer.start();
|
||||
|
||||
printTimer.initializeMs(1000, print).start();
|
||||
printTimer.start();
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef __COMMON_CONFIG_H__
|
||||
#define __COMMON_CONFIG_H__
|
||||
|
||||
#ifndef WIFI_SSID
|
||||
#define WIFI_SSID "╳╳╳"
|
||||
#define WIFI_PWD "bs7RFiQpgmMycfmt"
|
||||
//#define WIFI_SSID "hackerspace.pl-guests"
|
||||
//#define WIFI_PWD "HackThePlanet!"
|
||||
#endif
|
||||
|
||||
#ifndef MQTT_BROKER
|
||||
//#define MQTT_BROKER "10.8.1.16"
|
||||
#define MQTT_BROKER "10.1.10.50"
|
||||
#ifdef ENABLE_SSL
|
||||
#define MQTT_PORT 8883
|
||||
#define SSL_FINGERPRINT { } // TODO
|
||||
#else
|
||||
#define MQTT_PORT 1883
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define TOPIC_PREFIX "iot/"
|
||||
|
||||
#define OTA_URL "http://" MQTT_BROKER "/api/1/ota/"
|
||||
|
||||
#define BTN_PIN 0
|
||||
#define LED_PIN 2
|
||||
|
||||
#endif
|
|
@ -0,0 +1,45 @@
|
|||
#ifndef __USER_CONFIG_H__
|
||||
#define __USER_CONFIG_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// UART config
|
||||
#define SERIAL_BAUD_RATE 115200
|
||||
|
||||
// ESP SDK config
|
||||
#define LWIP_OPEN_SRC
|
||||
#define USE_US_TIMER
|
||||
|
||||
// Default types
|
||||
#define __CORRECT_ISO_CPP_STDLIB_H_PROTO
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Override c_types.h include and remove buggy espconn
|
||||
#define _C_TYPES_H_
|
||||
#define _NO_ESPCON_
|
||||
|
||||
// Updated, compatible version of c_types.h
|
||||
// Just removed types declared in <stdint.h>
|
||||
#include <espinc/c_types_compatible.h>
|
||||
|
||||
// System API declarations
|
||||
#include <esp_systemapi.h>
|
||||
|
||||
// C++ Support
|
||||
#include <esp_cplusplus.h>
|
||||
// Extended string conversion for compatibility
|
||||
#include <stringconversion.h>
|
||||
// Network base API
|
||||
#include <espinc/lwip_includes.h>
|
||||
|
||||
// Beta boards
|
||||
#define BOARD_ESP01
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,43 @@
|
|||
#include <freq_counter.h>
|
||||
|
||||
void freq_counter_init ( struct freq_counter *f, uint32_t cycle_len ) {
|
||||
f->input_initialized = false;
|
||||
f->input_state = false;
|
||||
|
||||
f->counter = 0;
|
||||
f->samples = 0;
|
||||
f->cycle_len = cycle_len;
|
||||
|
||||
f->new_measurement = false;
|
||||
f->measurement = 0;
|
||||
}
|
||||
|
||||
void IRAM_ATTR freq_counter_update ( struct freq_counter *f, bool input ) {
|
||||
if ( f->input_initialized == false ) {
|
||||
// ignore first input value
|
||||
f->input_state = input;
|
||||
f->input_initialized = true;
|
||||
}
|
||||
if ( f->input_state != input) {
|
||||
f->counter += 1;
|
||||
f->input_state = input;
|
||||
}
|
||||
f->samples += 1;
|
||||
if ( f->samples >= f->cycle_len ) {
|
||||
/* cycle complete */
|
||||
f->measurement = f->counter;
|
||||
f->new_measurement = true;
|
||||
f->samples = 0;
|
||||
f->counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool freq_counter_get_measurement( struct freq_counter *f, uint32_t *measurement) {
|
||||
if ( f->new_measurement == true ) {
|
||||
*measurement = f->measurement;
|
||||
f->new_measurement = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef FREQ_COUNTER_H
|
||||
#define FREQ_COUNTER_H
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR __attribute__((section(".iram.text")))
|
||||
#endif
|
||||
|
||||
struct freq_counter {
|
||||
bool input_initialized; /* false before first update call */
|
||||
bool input_state; /* last update input state */
|
||||
|
||||
uint32_t counter; /* input state changes counter */
|
||||
uint32_t samples; /* input samples counter */
|
||||
uint32_t cycle_len; /* number of samples in one cycle */
|
||||
|
||||
bool new_measurement; /* set to true after updating measurement */
|
||||
uint32_t measurement; /* number of state changes in last complete cycle */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief initialize frequency counter structure
|
||||
*
|
||||
* @param cycle_len number of samples in one measurement cycle
|
||||
*/
|
||||
void freq_counter_init ( struct freq_counter *f, uint32_t cycle_len );
|
||||
void IRAM_ATTR freq_counter_update ( struct freq_counter *f, bool input );
|
||||
|
||||
|
||||
/**
|
||||
* @brief get new measurement from freq_counter
|
||||
*
|
||||
* this function should be called at least twice per cycle in order to avoid
|
||||
* concurrent rw acces to measurement variable
|
||||
* @param measurement is set to new measurement value if returned true
|
||||
* @return true if new measurement is available
|
||||
*/
|
||||
bool freq_counter_get_measurement( struct freq_counter *f, uint32_t *measurement);
|
||||
|
||||
#endif /* end of include guard: FREQ_COUNTER_H */
|
|
@ -0,0 +1,51 @@
|
|||
#include <impuls_counter.h>
|
||||
|
||||
void impuls_counter_init ( struct impuls_counter *f, uint32_t cycle_len ) {
|
||||
f->input_initialized = false;
|
||||
f->input_state = false;
|
||||
|
||||
f->counter = 0;
|
||||
f->samples = 0;
|
||||
f->cycle_len = cycle_len;
|
||||
|
||||
f->new_measurement = false;
|
||||
f->measurement.samples = 0;
|
||||
f->measurement.changes = 0;
|
||||
}
|
||||
|
||||
void IRAM_ATTR impuls_counter_update ( struct impuls_counter *f, bool input ) {
|
||||
if ( f->input_initialized == false ) {
|
||||
// ignore first input value
|
||||
f->input_state = input;
|
||||
f->input_initialized = true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
f->samples += 1;
|
||||
if ( f->input_state != input) {
|
||||
changed = true;
|
||||
f->counter += 1;
|
||||
f->input_state = input;
|
||||
}
|
||||
|
||||
if ( f->samples >= f->cycle_len && (changed || f->counter == 0 \
|
||||
|| f->samples >= 2 * f->cycle_len)) {
|
||||
/* cycle complete */
|
||||
f->measurement.changes = f->counter;
|
||||
f->measurement.samples = f->samples;
|
||||
f->new_measurement = true;
|
||||
f->samples = 0;
|
||||
f->counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool impuls_counter_get_measurement( struct impuls_counter *f,
|
||||
struct impuls_measurement *measurement) {
|
||||
if ( f->new_measurement == true ) {
|
||||
*measurement = f->measurement;
|
||||
f->new_measurement = false;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef IMPULS_COUNTER_H
|
||||
#define IMPULS_COUNTER_H
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR __attribute__((section(".iram.text")))
|
||||
#endif
|
||||
|
||||
struct impuls_measurement {
|
||||
uint32_t samples; /* number of samples */
|
||||
uint32_t changes; /* input state changes */
|
||||
};
|
||||
|
||||
struct impuls_counter {
|
||||
bool input_initialized; /* false before first update call */
|
||||
bool input_state; /* last update input state */
|
||||
|
||||
uint32_t counter; /* input state changes counter */
|
||||
uint32_t samples; /* input samples counter */
|
||||
uint32_t cycle_len; /* minimum number of samples in one cycle */
|
||||
|
||||
bool new_measurement; /* set to true after updating measurement */
|
||||
struct impuls_measurement measurement; /* one measurement */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief initialize impuls counter structure
|
||||
*
|
||||
* @param cycle_len number of samples in one measurement cycle
|
||||
*/
|
||||
void impuls_counter_init ( struct impuls_counter *f, uint32_t cycle_len );
|
||||
|
||||
void IRAM_ATTR impuls_counter_update ( struct impuls_counter *f, bool input );
|
||||
|
||||
/**
|
||||
* @brief get new measurement from impuls_counter
|
||||
*
|
||||
* this function should be called at least twice per cycle in order to avoid
|
||||
* concurrent rw acces to measurement variable
|
||||
* @param measurement is set to new measurement value if returned true
|
||||
* @return true if new measurement is available
|
||||
*/
|
||||
bool impuls_counter_get_measurement( struct impuls_counter *f,
|
||||
struct impuls_measurement *measurement);
|
||||
|
||||
#endif /* end of include guard: IMPULS_COUNTER_H */
|
|
@ -0,0 +1,45 @@
|
|||
#include <SmingCore/SmingCore.h>
|
||||
|
||||
|
||||
extern "C" {
|
||||
#include <sonoff_pow.h>
|
||||
}
|
||||
|
||||
struct cse cse;
|
||||
|
||||
static struct impuls_counter power_counter;
|
||||
static struct impuls_counter current_counter;
|
||||
static struct impuls_counter voltage_counter;
|
||||
|
||||
void IRAM_ATTR cse_measure_isr ( void ) {
|
||||
bool cf, cf1;
|
||||
cf = digitalRead( CF_PIN );
|
||||
cf1 = digitalRead( CF1_PIN );
|
||||
impuls_counter_update( &power_counter, cf );
|
||||
impuls_counter_update( ¤t_counter, cf1 );
|
||||
}
|
||||
|
||||
void cse_init( uint32_t cycle_len ) {
|
||||
pinMode(CF1_PIN, INPUT);
|
||||
pinMode(CF_PIN, INPUT);
|
||||
pinMode(SEL_PIN, OUTPUT);
|
||||
impuls_counter_init( &power_counter, cycle_len);
|
||||
impuls_counter_init( ¤t_counter , cycle_len);
|
||||
|
||||
cse.power.samples = 0;
|
||||
cse.power.changes = 0;
|
||||
cse.current.samples = 0;
|
||||
cse.current.changes = 0;
|
||||
cse.voltage.samples = 0;
|
||||
cse.voltage.changes = 0;
|
||||
cse.energy = 0;
|
||||
}
|
||||
|
||||
void cse_update() {
|
||||
bool pm, cm;
|
||||
pm = impuls_counter_get_measurement( &power_counter, &cse.power );
|
||||
cm = impuls_counter_get_measurement( ¤t_counter, &cse.current );
|
||||
if ( pm == true ) {
|
||||
cse.energy += cse.power.changes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#include <impuls_counter.h>
|
||||
|
||||
#ifndef SONOFF_POW_H
|
||||
#define SONOFF_POW_H
|
||||
|
||||
#ifndef IRAM_ATTR
|
||||
#define IRAM_ATTR __attribute__((section(".iram.text")))
|
||||
#endif
|
||||
|
||||
#define CF_PIN 14
|
||||
#define CF1_PIN 13
|
||||
#define SEL_PIN 5
|
||||
#define RELAY_PIN 12
|
||||
|
||||
struct cse {
|
||||
struct impuls_measurement power;
|
||||
struct impuls_measurement current;
|
||||
struct impuls_measurement voltage;
|
||||
uint64_t energy;
|
||||
};
|
||||
|
||||
extern struct cse cse;
|
||||
|
||||
void IRAM_ATTR cse_measure_isr ( void );
|
||||
void cse_init( uint32_t cycle_len );
|
||||
void cse_update();
|
||||
|
||||
#endif /* end of include guard: SONOFF_POW_H */
|
|
@ -0,0 +1,33 @@
|
|||
#include <SpejsNode.h>
|
||||
#include <Endpoint.h>
|
||||
|
||||
void Endpoint::bind(String _name, SpejsNode* _parent) {
|
||||
parent = _parent;
|
||||
name = _name;
|
||||
}
|
||||
|
||||
void Endpoint::notify(String property, String value) {
|
||||
if(parent)
|
||||
parent->notify(name + "/" + property, value);
|
||||
}
|
||||
|
||||
void Endpoint::onConnected() {
|
||||
parent->subscribe(name + "/+/set");
|
||||
parent->notify(name + "/$type", type);
|
||||
}
|
||||
|
||||
void Endpoint::onMessage(String topic, String payload) {
|
||||
String devicePrefix = parent->DEV_TOPIC("");
|
||||
|
||||
if(!topic.startsWith(devicePrefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int propPos = topic.indexOf("/", devicePrefix.length());
|
||||
String endpoint = topic.substring(devicePrefix.length(), propPos);
|
||||
String property = topic.substring(propPos+1, topic.indexOf("/", propPos+1));
|
||||
|
||||
if(name.equals(endpoint)) {
|
||||
debugf("%s - %s response: %d\n", endpoint.c_str(), property.c_str(), onValue(property, payload).status);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef ENDPOINT_H
|
||||
#define ENDPOINT_H
|
||||
|
||||
#include <SmingCore/SmingCore.h>
|
||||
|
||||
class SpejsNode;
|
||||
|
||||
class EndpointResult {
|
||||
public:
|
||||
int status;
|
||||
String description;
|
||||
|
||||
EndpointResult(int _status) : status(_status) {}
|
||||
EndpointResult(int _status, String _description) :
|
||||
status(_status), description(_description) {}
|
||||
};
|
||||
|
||||
class Endpoint {
|
||||
protected:
|
||||
SpejsNode* parent;
|
||||
String name;
|
||||
|
||||
public:
|
||||
String type;
|
||||
|
||||
Endpoint(String _type = "unknown") : type(_type) { }
|
||||
|
||||
virtual void bind(String _name, SpejsNode* _parent);
|
||||
void notify(String property, String value);
|
||||
|
||||
virtual void onMessage(String topic, String payload);
|
||||
virtual EndpointResult onValue(String property, String value) {
|
||||
return 400;
|
||||
}
|
||||
|
||||
virtual void onConnected();
|
||||
|
||||
static Endpoint* fromJSON(JsonObject& obj) {
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> class ValueEndpoint : public Endpoint {
|
||||
protected:
|
||||
T value;
|
||||
void updateValue(T newValue);
|
||||
|
||||
public:
|
||||
ValueEndpoint(String _type) : Endpoint(_type) {}
|
||||
};
|
||||
|
||||
typedef Endpoint* (*EndpointInitializer) (JsonObject&);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,2 @@
|
|||
spejsiot provided by:
|
||||
http://wiki.hackerspace.pl/projects:spejsiot
|
|
@ -0,0 +1,221 @@
|
|||
#include <SpejsNode.h>
|
||||
#include <Endpoint.h>
|
||||
#include <ver.h>
|
||||
|
||||
#include <endpoints/ImplementationEndpoint.h>
|
||||
|
||||
#define CONFIG_FILE "config.json"
|
||||
|
||||
void SpejsNode::init() {
|
||||
deviceID = WifiStation.getMAC().substring(6, 12);
|
||||
|
||||
currentSlot = 0;
|
||||
if(!rboot_get_last_boot_rom(¤tSlot)) {
|
||||
currentSlot = rboot_get_current_rom();
|
||||
}
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial.systemDebugOutput(false); // Debug output to serial
|
||||
|
||||
debugf("*** SpejsNode init, runnning on: %s, current rom: %d", deviceID.c_str(), currentSlot);
|
||||
|
||||
WifiAccessPoint.enable(false);
|
||||
WifiStation.enable(true);
|
||||
WifiStation.config(WIFI_SSID, WIFI_PWD);
|
||||
|
||||
WifiEvents.onStationGotIP(StationGotIPDelegate(&SpejsNode::gotIP, this));
|
||||
|
||||
//registerEndpoint("$implementation", new ImplementationEndpoint());
|
||||
|
||||
// Keepalive Timer initialization
|
||||
keepaliveTimer.initializeMs(10000, TimerDelegate(&SpejsNode::keepAliveHandler, this)).start();
|
||||
|
||||
statusLED.high();
|
||||
}
|
||||
|
||||
void SpejsNode::loadJSON(std::vector<EndpointInitializer> initializers) {
|
||||
#ifdef RBOOT_SPIFFS_0
|
||||
debugf("trying to mount spiffs at 0x%08x, length %d", RBOOT_SPIFFS_0, SPIFF_SIZE);
|
||||
spiffs_mount_manual(RBOOT_SPIFFS_0, SPIFF_SIZE);
|
||||
#else
|
||||
debugf("trying to mount spiffs at 0x%08x, length %d", 0x100000, SPIFF_SIZE);
|
||||
spiffs_mount_manual(0x100000, SPIFF_SIZE);
|
||||
#endif
|
||||
|
||||
DynamicJsonBuffer jsonBuffer;
|
||||
if (fileExist(CONFIG_FILE)) {
|
||||
int size = fileGetSize(CONFIG_FILE);
|
||||
debugf("Found config file, %d bytes", size);
|
||||
char* jsonString = new char[size + 1];
|
||||
fileGetContent(CONFIG_FILE, jsonString, size + 1);
|
||||
JsonObject& root = jsonBuffer.parseObject(jsonString);
|
||||
|
||||
if (root.containsKey("name"))
|
||||
deviceType = (root["name"]).asString();
|
||||
|
||||
JsonObject& data = root["endpoints"].asObject();
|
||||
for (auto it: data) {
|
||||
bool found = false;
|
||||
|
||||
for(auto init: initializers) {
|
||||
Endpoint* ep = init(it.value);
|
||||
if (ep != NULL) {
|
||||
debugf("%s: got object", it.key);
|
||||
registerEndpoint(it.key, ep);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
debugf("%s: nothing found", it.key);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugf("No configuration");
|
||||
}
|
||||
}
|
||||
|
||||
void SpejsNode::keepAliveHandler() {
|
||||
static int failureCounter = 0;
|
||||
if(!WifiStation.isConnected()) {
|
||||
statusLED.high();
|
||||
debugf("keepalive: Network reconnect");
|
||||
if(failureCounter++ < 5)
|
||||
WifiStation.connect();
|
||||
else
|
||||
System.restart();
|
||||
} else if(mqtt.getConnectionState() != eTCS_Connected) {
|
||||
statusLED.high();
|
||||
debugf("keepalive: MQTT reconnect");
|
||||
if(failureCounter++ < 5)
|
||||
onConnected();
|
||||
else
|
||||
System.restart();
|
||||
} else {
|
||||
statusLED.idle();
|
||||
failureCounter = 0;
|
||||
|
||||
uint8_t mode;
|
||||
if(rboot_get_last_boot_mode(&mode) && mode == MODE_TEMP_ROM) {
|
||||
rboot_set_current_rom(currentSlot);
|
||||
debugf("Successfuly connected, accepting temp rom");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline String SpejsNode::DEV_TOPIC(String t) {
|
||||
return TOPIC_PREFIX + deviceID + "/" + t;
|
||||
}
|
||||
|
||||
void SpejsNode::httpIndex(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
response.sendString("This is spejsiot device, take a look at: https://wiki.hackerspace.pl/projects:spejsiot\n"
|
||||
"\nDevice type: " + deviceType +
|
||||
"\nFirmware version: " + String(BUILD_ID) +
|
||||
"\nMAC: " + WifiStation.getMAC());
|
||||
}
|
||||
|
||||
/*
|
||||
* Successful network connection handler
|
||||
*/
|
||||
void SpejsNode::gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway) {
|
||||
onConnected();
|
||||
}
|
||||
|
||||
void SpejsNode::onConnected() {
|
||||
statusLED.idle();
|
||||
|
||||
debugf("Connection successful");
|
||||
|
||||
// MQTT initialization
|
||||
mqtt.setWill(DEV_TOPIC("$online"), "false", 1, true);
|
||||
|
||||
#ifdef ENABLE_SSL
|
||||
const uint8_t sha1Fingerprint[] = SSL_FINGERPRINT;
|
||||
mqtt.connect("iot-" + deviceID, "", "", true);
|
||||
mqtt.addSslOptions(SSL_SERVER_VERIFY_LATER);
|
||||
mqtt.setSslFingerprint(sha1Fingerprint, 20);
|
||||
#else
|
||||
mqtt.connect("iot-" + deviceID);
|
||||
#endif
|
||||
|
||||
for(unsigned int i = 0 ; i < endpoints.count() ; i++) {
|
||||
endpoints.valueAt(i)->onConnected();
|
||||
}
|
||||
if ( onConnectedCallback != NULL ) {
|
||||
(*onConnectedCallback)();
|
||||
}
|
||||
|
||||
subscribe("$implementation/+");
|
||||
|
||||
// Say hello
|
||||
notify("$online", "true");
|
||||
notify("$homie", "2");
|
||||
notify("$name", deviceType);
|
||||
notify("$localip", WifiStation.getIP().toString());
|
||||
notify("$mac", WifiStation.getMAC());
|
||||
notify("$fw/name", "spejsiot");
|
||||
notify("$fw/version", BUILD_ID);
|
||||
notify("$implementation/slot", String(currentSlot));
|
||||
|
||||
// HTTP initialization
|
||||
http.listen(80);
|
||||
http.addPath("/", HttpPathDelegate(&SpejsNode::httpIndex, this));
|
||||
http.setDefaultHandler(HttpPathDelegate(&SpejsNode::httpFile, this));
|
||||
|
||||
}
|
||||
|
||||
void SpejsNode::httpFile(HttpRequest &request, HttpResponse &response)
|
||||
{
|
||||
String file = request.getPath();
|
||||
|
||||
if (file[0] == '/')
|
||||
file = file.substring(1);
|
||||
|
||||
if (file.startsWith("api/1/")) {
|
||||
String req = file.substring(6);
|
||||
String key = req.substring(0, req.indexOf("/"));
|
||||
String value = req.substring(req.indexOf("/") + 1);
|
||||
|
||||
if(key.length() == 0 || value.length() == 0 || !endpoints.contains(key)) {
|
||||
response.code = 400;
|
||||
} else {
|
||||
EndpointResult result = endpoints[key]->onValue(key, value);
|
||||
JsonObjectStream* stream = new JsonObjectStream();
|
||||
JsonObject& json = stream->getRoot();
|
||||
json["status"] = result.status;
|
||||
response.sendJsonObject(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Publish on device-specific topic
|
||||
*/
|
||||
bool SpejsNode::notify(String key, String value) {
|
||||
mqtt.publish(DEV_TOPIC(key), value, true);
|
||||
return mqtt.getConnectionState() == eTCS_Connected;
|
||||
}
|
||||
|
||||
/*
|
||||
* Subsribe to device-specific topic
|
||||
*/
|
||||
bool SpejsNode::subscribe(String topic) {
|
||||
mqtt.subscribe(DEV_TOPIC(topic));
|
||||
return mqtt.getConnectionState() == eTCS_Connected;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register new endpoint
|
||||
*/
|
||||
void SpejsNode::registerEndpoint(String key, Endpoint* endpoint) {
|
||||
endpoints[key] = endpoint;
|
||||
endpoint->bind(key, this);
|
||||
}
|
||||
|
||||
void SpejsNode::mqttCallback(String origtopic, String value) {
|
||||
for(unsigned int i = 0 ; i < endpoints.count() ; i++) {
|
||||
endpoints.valueAt(i)->onMessage(origtopic, value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
#ifndef SPEJSNODE_H
|
||||
#define SPEJSNODE_H
|
||||
|
||||
#include <user_config.h>
|
||||
#include <common_config.h>
|
||||
#include <SmingCore/SmingCore.h>
|
||||
#include <Endpoint.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define D0 16
|
||||
#define D1 5
|
||||
#define D2 4
|
||||
#define D3 0
|
||||
#define D4 2
|
||||
#define D5 14
|
||||
#define D6 12
|
||||
#define D7 13
|
||||
#define D8 15
|
||||
|
||||
class LED {
|
||||
int highState = HIGH;
|
||||
int pin = 2;
|
||||
Timer animateTimer;
|
||||
|
||||
void animate() {
|
||||
if (millis() % blinkRate > blinkOn) {
|
||||
digitalWrite(pin, !highState);
|
||||
} else {
|
||||
digitalWrite(pin, highState);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int blinkRate = 4000;
|
||||
int blinkOn = 100;
|
||||
|
||||
LED() { }
|
||||
|
||||
void config(int pin_, bool highState_ = HIGH) {
|
||||
pin = pin_;
|
||||
highState = highState_;
|
||||
|
||||
pinMode(pin, OUTPUT);
|
||||
animateTimer.initializeMs(50, TimerDelegate(&LED::animate, this)).start();
|
||||
}
|
||||
|
||||
void idle() {
|
||||
blinkRate = 4000;
|
||||
}
|
||||
|
||||
void high() {
|
||||
blinkRate = 300;
|
||||
}
|
||||
};
|
||||
|
||||
class SpejsNode {
|
||||
protected:
|
||||
Timer keepaliveTimer;
|
||||
|
||||
HashMap<String, Endpoint*> endpoints;
|
||||
|
||||
void keepAliveHandler();
|
||||
void initializeMDNS();
|
||||
|
||||
void buildMetadata(JsonObjectStream* stream);
|
||||
|
||||
void mqttCallback(String, String);
|
||||
void otaUpdateCallback(bool result);
|
||||
void httpFile(HttpRequest &request, HttpResponse &response);
|
||||
void httpIndex(HttpRequest &request, HttpResponse &response);
|
||||
public:
|
||||
MqttClient mqtt;
|
||||
HttpServer http;
|
||||
|
||||
String deviceID;
|
||||
String deviceType;
|
||||
|
||||
LED statusLED;
|
||||
|
||||
void (*onConnectedCallback)(void);
|
||||
|
||||
uint8_t currentSlot;
|
||||
|
||||
SpejsNode(String _deviceType) :
|
||||
mqtt(MQTT_BROKER, MQTT_PORT, MqttStringSubscriptionCallback(&SpejsNode::mqttCallback, this)),
|
||||
deviceType(_deviceType), onConnectedCallback(NULL) {};
|
||||
|
||||
void onConnected();
|
||||
void gotIP(IPAddress ip, IPAddress netmask, IPAddress gateway);
|
||||