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);
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
bool notify(String key, String value);
|
||||||
|
bool subscribe(String topic);
|
||||||
|
|
||||||
|
void registerEndpoint(String key, Endpoint* cb);
|
||||||
|
|
||||||
|
String DEV_TOPIC(String t);
|
||||||
|
|
||||||
|
void loadJSON(std::vector<EndpointInitializer>);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,78 @@
|
||||||
|
#include <endpoints/ImplementationEndpoint.h>
|
||||||
|
#include <rboot-api.h>
|
||||||
|
|
||||||
|
EndpointResult ImplementationEndpoint::onValue(String property, String value) {
|
||||||
|
if (property == "ota" and value == "true") {
|
||||||
|
startOTA();
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImplementationEndpoint::startOTA() {
|
||||||
|
uint8_t slot;
|
||||||
|
rboot_config bootconf;
|
||||||
|
|
||||||
|
#ifdef RBOOT_TWO_ROMS
|
||||||
|
String romURL = OTA_URL + parent->deviceID + (parent->currentSlot ? "/rom1.bin" : "/rom0.bin");
|
||||||
|
#else
|
||||||
|
String romURL = OTA_URL + parent->deviceID + "/rom0.bin";
|
||||||
|
#endif
|
||||||
|
String spiffsURL = OTA_URL + parent->deviceID + "/spiff_rom.bin";
|
||||||
|
|
||||||
|
debugf("Updating...");
|
||||||
|
|
||||||
|
// need a clean object, otherwise if run before and failed will not run again
|
||||||
|
if (otaUpdater) delete otaUpdater;
|
||||||
|
otaUpdater = new rBootHttpUpdate();
|
||||||
|
|
||||||
|
bootconf = rboot_get_config();
|
||||||
|
|
||||||
|
slot = !parent->currentSlot;
|
||||||
|
|
||||||
|
debugf("Updating to rom %d.", slot);
|
||||||
|
|
||||||
|
// flash rom to position indicated in the rBoot config rom table
|
||||||
|
otaUpdater->addItem(bootconf.roms[slot], romURL);
|
||||||
|
|
||||||
|
#ifndef DISABLE_SPIFFS
|
||||||
|
// use user supplied values (defaults for 4mb flash in makefile)
|
||||||
|
if (slot == 0) {
|
||||||
|
otaUpdater->addItem(RBOOT_SPIFFS_0, spiffsURL);
|
||||||
|
} else {
|
||||||
|
otaUpdater->addItem(RBOOT_SPIFFS_1, spiffsURL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
otaUpdater->setCallback(OtaUpdateDelegate(&ImplementationEndpoint::otaUpdateCallback, this));
|
||||||
|
otaUpdater->start();
|
||||||
|
|
||||||
|
notify("ota", "started");
|
||||||
|
|
||||||
|
parent->statusLED.high();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImplementationEndpoint::otaUpdateCallback(rBootHttpUpdate& updater, bool result) {
|
||||||
|
parent->statusLED.idle();
|
||||||
|
|
||||||
|
if(result == true) {
|
||||||
|
// success
|
||||||
|
notify("ota", "finished");
|
||||||
|
|
||||||
|
uint8 slot;
|
||||||
|
|
||||||
|
if (parent->currentSlot == 0)
|
||||||
|
slot = 1;
|
||||||
|
else
|
||||||
|
slot = 0;
|
||||||
|
|
||||||
|
// set to boot new rom and then reboot
|
||||||
|
debugf("Firmware updated, rebooting to rom %d...", slot);
|
||||||
|
|
||||||
|
rboot_set_temp_rom(slot);
|
||||||
|
|
||||||
|
System.restart();
|
||||||
|
} else {
|
||||||
|
notify("ota", "failed");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef IMPLEMENTATIONENDPOINT_H
|
||||||
|
#define IMPLEMENTATIONENDPOINT_H
|
||||||
|
|
||||||
|
#include <Endpoint.h>
|
||||||
|
#include <SpejsNode.h>
|
||||||
|
|
||||||
|
class ImplementationEndpoint : public Endpoint {
|
||||||
|
protected:
|
||||||
|
rBootHttpUpdate* otaUpdater = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Use empty endpoint type to just keep it unpublished
|
||||||
|
ImplementationEndpoint() : Endpoint("") {}
|
||||||
|
|
||||||
|
EndpointResult onValue(String property, String value);
|
||||||
|
void otaUpdateCallback(rBootHttpUpdate& updater, bool result);
|
||||||
|
void startOTA();
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include <endpoints/OutputEndpoint.h>
|
||||||
|
#include <SpejsNode.h>
|
||||||
|
|
||||||
|
EndpointResult OutputEndpoint::onValue(String property, String value) {
|
||||||
|
if (value == "1" or value == "on" or value == "true") {
|
||||||
|
currentValue = 1;
|
||||||
|
} else if (value == "0" or value == "off" or value == "false") {
|
||||||
|
currentValue = 0;
|
||||||
|
} else {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentValue != (parent->statusLED.blinkOn > parent->statusLED.blinkRate/2)) {
|
||||||
|
parent->statusLED.blinkOn = parent->statusLED.blinkRate - parent->statusLED.blinkOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
digitalWrite(pin, inverted ^ currentValue);
|
||||||
|
notify("on", currentValue ? "true" : "false");
|
||||||
|
return 200;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef OUTPUTENDPOINT_H
|
||||||
|
#define OUTPUTENDPOINT_H value
|
||||||
|
|
||||||
|
#include <Endpoint.h>
|
||||||
|
|
||||||
|
class OutputEndpoint : public Endpoint {
|
||||||
|
private:
|
||||||
|
int pin;
|
||||||
|
bool inverted;
|
||||||
|
bool currentValue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OutputEndpoint(int _pin, bool _inverted = false) : Endpoint("output"),
|
||||||
|
pin(_pin), inverted(_inverted), currentValue(inverted) {
|
||||||
|
pinMode(pin, OUTPUT);
|
||||||
|
digitalWrite(pin, currentValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointResult onValue(String property, String value);
|
||||||
|
|
||||||
|
static Endpoint* fromJSON(JsonObject& obj) {
|
||||||
|
if (String(obj["type"].asString()) == "output" && obj.containsKey("gpio")) {
|
||||||
|
return new OutputEndpoint(obj["gpio"], obj["inverted"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* ifndef OUTPUTENDPOINT_H */
|
|
@ -0,0 +1 @@
|
||||||
|
extern char* BUILD_ID;
|
Loading…
Reference in New Issue