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 TBD
master
vuko 2018-03-20 10:40:47 +01:00
commit 6489e78cdc
36 changed files with 1604 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
*.d
*.o
*.elf
*.hex
*.map
*.swp
*.swo
*.*.sw*
*.obj
*.gdb_history
*__pycache__*
**/debug/
**/build

45
README.mk Normal file
View File

@ -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.

3
cfg/env Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
source "${PROJECT_DIR}/cfg/env_spacenode"
alias pd="cd $(realpath ${PROJECT_DIR})"

9
cfg/env_spacenode Normal file
View File

@ -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"

3
cfg/noproject_env Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/bash
export PROJECT_DIR=$(realpath ./)
source cfg/env

1
cfg/vim Normal file
View File

@ -0,0 +1 @@
Project cfg/vimproject

45
cfg/vimproject Normal file
View File

@ -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
}
}
}

30
doc/cse7759.py Normal file
View File

@ -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

22
doc/hw.txt Normal file
View File

@ -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

27
init.sh Executable file
View File

@ -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}"

123
soft/CMakeLists.txt Normal file
View File

@ -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)

12
soft/Makefile Normal file
View File

@ -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

242
soft/platform/common.ld Normal file
View File

@ -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"

View File

@ -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"

View File

@ -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"

28
soft/src/TcpPublish.cpp Normal file
View File

@ -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;
}
}
}

12
soft/src/TcpPublish.h Normal file
View File

@ -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);
};

74
soft/src/application.cpp Normal file
View File

@ -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();
}

View File

@ -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

View File

@ -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

43
soft/src/freq_counter.c Normal file
View File

@ -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;
}
}

42
soft/src/freq_counter.h Normal file
View File

@ -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 */

51
soft/src/impuls_counter.c Normal file
View File

@ -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;
}
}

48
soft/src/impuls_counter.h Normal file
View File

@ -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 */

45
soft/src/sonoff_pow.cpp Normal file
View File

@ -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( &current_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( &current_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( &current_counter, &cse.current );
if ( pm == true ) {
cse.energy += cse.power.changes;
}
}

28
soft/src/sonoff_pow.h Normal file
View File

@ -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 */

View File

@ -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);
}
}

View File

@ -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

2
soft/src/spejsiot/README Normal file
View File

@ -0,0 +1,2 @@
spejsiot provided by:
http://wiki.hackerspace.pl/projects:spejsiot

View File

@ -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(&currentSlot)) {
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);
}
}

View File

@ -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);