diff --git a/.github/workflows/Tasmota_build_development.yml b/.github/workflows/Tasmota_build_development.yml index f36fec5a8..63114faf0 100644 --- a/.github/workflows/Tasmota_build_development.yml +++ b/.github/workflows/Tasmota_build_development.yml @@ -1351,3 +1351,17 @@ jobs: destination_repo: 'arendst/Tasmota-firmware' user_email: 'github-actions@github.com' user_name: 'github-actions' + - name: Creat trigger.txt + run: | + echo ${GITHUB_SHA} &> trigger.txt + echo "$( trigger.txt + echo "$( #define READLINE_STEP 100 @@ -73,36 +76,105 @@ static int l_input(bvm *vm) return m_readline(vm); } +/* Look in the current class and all super classes for a method corresponding to a specific closure pointer */ +static bclass *find_class_closure(bclass *cl, bclosure *needle) +{ + while (cl) { + bmapnode *node; /* iterate on members of the class */ + bmap *members = be_class_members(cl); + if (members) { /* only iterate if there are members */ + bmapiter iter = be_map_iter(); + while ((node = be_map_next(members, &iter)) != NULL) { + if (var_type(&node->value) == BE_CLOSURE) { /* only native functions are considered */ + bclosure *clos_iter = var_toobj(&node->value); /* retrieve the method's closure */ + if (clos_iter == needle) { + /* we found the closure, we now know its class */ + return cl; + } + } + } + } + cl = be_class_super(cl); /* move to super class */ + } + return NULL; /* not found */ +} + static int l_super(bvm *vm) { int argc = be_top(vm); - if (argc) { + + /* if no argument, or arg 1 is nil, return nil */ + if (argc == 0 || be_isnil(vm, 1)) { + be_return_nil(vm); + } + + /* if arg 1 is a class, simply return super */ + if (be_isclass(vm, 1)) { + be_getsuper(vm, 1); + be_return(vm); + } + + /* arg 1 is an instance */ + if (be_isinstance(vm, 1)) { + binstance *o = var_toobj(be_indexof(vm, 1)); + bclass *target_class = NULL; /* the minimal class expected, or any super class */ + bclass *base_class = NULL; /* current class of the caller, if any */ + + /* if arg 2 is present, it must be a class */ if (argc >= 2) { - if (be_isinstance(vm, 1) && be_isclass(vm, 2)) { - /* leveled super, i.e. fix the parenthood class level */ - binstance *o = var_toobj(be_indexof(vm, 1)); - bclass *bc = var_toobj(be_indexof(vm, 2)); - while (o) { - bclass *c = be_instance_class(o); - if (c == bc) break; /* found */ - o = be_instance_super(o); - } - bvalue *top = be_incrtop(vm); - if (o) { - var_setinstance(top, o); /* return the instance with the specified parent class */ - } else { - var_setnil(top); /* not found, return nil */ - } - be_return(vm); + if (be_isclass(vm, 2)) { + target_class = var_toobj(be_indexof(vm, 2)); + } else if (be_isnil(vm, 2)) { + // ignore, revert to standard super() behavior if second arg is explicit nil } else { be_raise(vm, "type_error", "leveled super() requires 'instance' and 'class' arguments"); } + } + + /* now the more complex part, if arg 1 is an instance */ + /* if instance is the sole argument, try to find if it comes from a method of a class and set 'base_class' accordinly */ + /* later it will be equivalent to passing this class as second argument */ + if (argc == 1) { + /* we look in the callstack for the caller's closure */ + int size = be_stack_count(&vm->callstack); + if (size >= 2) { /* need at least 2 stackframes: current (for super() native) and caller (the one we are interested in) */ + bcallframe *caller = be_vector_at(&vm->callstack, size - 2); /* get the callframe of caller */ + bvalue *func = caller->func; /* function object of caller */ + if (var_type(func) == BE_CLOSURE) { /* only useful if the caller is a Berry closure (i.e. not native) */ + bclosure *clos_ctx = var_toobj(func); /* this is the closure we look for in the class chain */ + base_class = find_class_closure(o->_class, clos_ctx); /* iterate on current and super classes to find where the closure belongs */ + } + } + } + + if (base_class || target_class) { + if (base_class) { + target_class = base_class->super; + if (!target_class) be_return_nil(vm); /* fast exit if top class */ + } + /* leveled super, i.e. fix the parenthood class level */ + if (o) { + o = be_instance_super(o); /* always skip the current class and move to super */ + } + while (o) { + bclass *c = be_instance_class(o); + if (c == target_class) break; /* found */ + o = be_instance_super(o); + } + bvalue *top = be_incrtop(vm); + if (o) { + var_setinstance(top, o); /* return the instance with the specified parent class */ + } else { + var_setnil(top); /* not found, return nil */ + } + be_return(vm); } else { - /* simple use of super */ be_getsuper(vm, 1); be_return(vm); } } + + /* fall through, return nil if we don't know what to do */ be_return_nil(vm); } diff --git a/lib/libesp32/Berry/src/be_bytecode.c b/lib/libesp32/Berry/src/be_bytecode.c index 0e0cbf723..521317e63 100644 --- a/lib/libesp32/Berry/src/be_bytecode.c +++ b/lib/libesp32/Berry/src/be_bytecode.c @@ -137,14 +137,21 @@ static bstring** save_members(bvm *vm, void *fp, bclass *c, int nvar) } else { /* save method's name and function */ bproto *proto; bvalue *value = &node->value; - be_assert(var_isclosure(value) || var_isproto(value)); + be_assert(var_isclosure(value) || var_isproto(value) || var_isnil(value)); save_string(fp, var_tostr(&node->key)); /* save method name */ if (var_isproto(value)) { /* the method is a prototype */ proto = var_toobj(value); - } else { /* the method is a closure */ - proto = cast(bclosure *, var_toobj(value))->proto; + save_proto(vm, fp, proto); /* only save prototype */ + } else if (var_isclosure(value)) { /* the method is a closure */ + proto = cast(bclosure *, var_toobj(value))->proto; + save_proto(vm, fp, proto); /* only save prototype */ + } else if (var_isnil(value)) { + /* this is a static member (nil default) */ + save_word(fp, 0); /* store a zero byte that will be seen as a zero length method name which is invalid */ + } else { + be_raise(vm, "internal_error", "unsupported member in class"); + return NULL; /* should never be executed */ } - save_proto(vm, fp, proto); /* only save prototype */ } } return vars; @@ -286,7 +293,7 @@ void be_bytecode_save(bvm *vm, const char *filename, bproto *proto) #endif /* BE_USE_BYTECODE_SAVER */ #if BE_USE_BYTECODE_LOADER -static void load_proto(bvm *vm, void *fp, bproto **proto, int info, int version); +static bbool load_proto(bvm *vm, void *fp, bproto **proto, int info, int version); static uint8_t load_byte(void *fp) { @@ -416,8 +423,13 @@ static void load_class(bvm *vm, void *fp, bvalue *v, int version) value = vm->top; var_setproto(value, NULL); be_incrtop(vm); - load_proto(vm, fp, (bproto**)&var_toobj(value), -3, version); - be_method_bind(vm, c, name, var_toobj(value)); + if (load_proto(vm, fp, (bproto**)&var_toobj(value), -3, version)) { + /* actual method */ + be_method_bind(vm, c, name, var_toobj(value)); + } else { + /* no proto, static member set to nil */ + be_member_bind(vm, c, name, bfalse); + } be_stackpop(vm, 2); /* pop the cached string and proto */ } for (count = 0; count < nvar; ++count) { /* load member-variable table */ @@ -509,21 +521,28 @@ static void load_upvals(bvm *vm, void *fp, bproto *proto) } } -static void load_proto(bvm *vm, void *fp, bproto **proto, int info, int version) +static bbool load_proto(bvm *vm, void *fp, bproto **proto, int info, int version) { - *proto = be_newproto(vm); - (*proto)->name = load_string(vm, fp); - (*proto)->source = load_string(vm, fp); - (*proto)->argc = load_byte(fp); - (*proto)->nstack = load_byte(fp); - if (version > 1) { - (*proto)->varg = load_byte(fp); - load_byte(fp); /* discard reserved byte */ + /* first load the name */ + /* if empty, it's a static member so don't allocate an actual proto */ + bstring *name = load_string(vm, fp); + if (str_len(name)) { + *proto = be_newproto(vm); + (*proto)->name = name; + (*proto)->source = load_string(vm, fp); + (*proto)->argc = load_byte(fp); + (*proto)->nstack = load_byte(fp); + if (version > 1) { + (*proto)->varg = load_byte(fp); + load_byte(fp); /* discard reserved byte */ + } + load_bytecode(vm, fp, *proto, info); + load_constant(vm, fp, *proto, version); + load_proto_table(vm, fp, *proto, version); + load_upvals(vm, fp, *proto); + return btrue; } - load_bytecode(vm, fp, *proto, info); - load_constant(vm, fp, *proto, version); - load_proto_table(vm, fp, *proto, version); - load_upvals(vm, fp, *proto); + return bfalse; /* no proto read */ } void load_global_info(bvm *vm, void *fp) diff --git a/lib/libesp32/Berry/src/be_byteslib.c b/lib/libesp32/Berry/src/be_byteslib.c index 5211a0a4c..e63f1932f 100644 --- a/lib/libesp32/Berry/src/be_byteslib.c +++ b/lib/libesp32/Berry/src/be_byteslib.c @@ -419,26 +419,17 @@ static void buf_add_hex(buf_impl* buf, const char *hex, size_t len) /******************************************************************** ** Wrapping into lib ********************************************************************/ -// typedef int (*bntvfunc)(bvm*); /* native function pointer */ -int free_bytes_buf(bvm* vm) -{ - int argc = be_top(vm); - if (argc > 0) { - buf_impl * buf = (buf_impl*) be_tocomptr(vm, 1); - if (buf != NULL) { - be_os_free(buf); - } - } - be_return_nil(vm); -} -buf_impl * bytes_alloc(int32_t size) +buf_impl * bytes_realloc(bvm *vm, buf_impl *oldbuf, int32_t size) { if (size < 4) { size = 4; } if (size > BYTES_MAX_SIZE) { size = BYTES_MAX_SIZE; } - buf_impl * next = (buf_impl*) be_os_malloc(size + BYTES_OVERHEAD); + size_t oldsize = oldbuf ? oldbuf->size + BYTES_OVERHEAD : 0; + buf_impl * next = (buf_impl*) be_realloc(vm, oldbuf, oldsize, size + BYTES_OVERHEAD); /* malloc */ next->size = size; - next->len = 0; + if (!oldbuf) { + next->len = 0; /* allocate a new buffer */ + } return next; } @@ -467,10 +458,10 @@ static int m_init(bvm *vm) } else if (argc > 1 && be_isstring(vm, 2)) { hex_in = be_tostring(vm, 2); if (hex_in) { - size = strlen(hex_in) / 2 + BYTES_HEADROOM; // allocate headroom + size = strlen(hex_in) / 2 + BYTES_HEADROOM; /* allocate headroom */ } } - buf_impl * buf = bytes_alloc(size); + buf_impl * buf = bytes_realloc(vm, NULL, size); /* allocate new buffer */ if (!buf) { be_throw(vm, BE_MALLOC_FAIL); } @@ -478,22 +469,35 @@ static int m_init(bvm *vm) if (hex_in) { buf_add_hex(buf, hex_in, strlen(hex_in)); } - be_newcomobj(vm, buf, &free_bytes_buf); + be_pushcomptr(vm, buf); be_setmember(vm, 1, ".p"); be_return_nil(vm); } +/* deallocate buffer */ +static int m_deinit(bvm *vm) { +{ + be_getmember(vm, 1, ".p"); + buf_impl * buf = be_tocomptr(vm, -1); + be_pop(vm, 1); + if (buf != NULL) { + be_realloc(vm, buf, buf->size + BYTES_OVERHEAD, 0); + } + be_pushcomptr(vm, NULL); /* push NULL pointer instead, just in case */ + be_setmember(vm, 1, ".p"); + be_return_nil(vm); +} +} + /* grow or shrink to the exact value */ /* stack item 1 must contain the instance */ static buf_impl * _bytes_resize(bvm *vm, buf_impl * buf, size_t new_size) { - buf_impl * new_buf = bytes_alloc(new_size); + buf_impl *new_buf = bytes_realloc(vm, buf, new_size); if (!new_buf) { be_throw(vm, BE_MALLOC_FAIL); } - memmove(buf_get_buf(new_buf), buf_get_buf(buf), buf->len); - new_buf->len = buf->len; - /* replace the .p attribute */ - be_newcomobj(vm, new_buf, &free_bytes_buf); + /* replace the .p attribute since address may have changed */ + be_pushcomptr(vm, new_buf); be_setmember(vm, 1, ".p"); be_pop(vm, 1); /* remove comobj from stack */ /* the old buffer will be garbage collected later */ @@ -504,7 +508,13 @@ static buf_impl * _bytes_resize(bvm *vm, buf_impl * buf, size_t new_size) { /* if grow, then add some headroom */ /* stack item 1 must contain the instance */ static buf_impl * bytes_resize(bvm *vm, buf_impl * buf, size_t new_size) { - if (buf->size >= new_size) { return buf; } /* no resize needed */ + /* when resized to smaller, we introduce a new heurstic */ + /* If the buffer is 64 bytes or smaller, don't shrink */ + /* Shrink buffer only if target size is smaller than half the original size */ + if (buf->size >= new_size) { /* enough room, consider if need to shrink */ + if (buf->size <= 64) { return buf; } /* don't shrink if below 64 bytes */ + if (buf->size < new_size * 2) { return buf; } + } return _bytes_resize(vm, buf, new_size + BYTES_HEADROOM); } @@ -1226,6 +1236,7 @@ void be_load_byteslib(bvm *vm) { ".p", NULL }, { "_buffer", m_buffer }, { "init", m_init }, + { "deinit", m_deinit }, { "tostring", m_tostring }, { "asstring", m_asstring }, { "fromstring", m_fromstring }, @@ -1261,6 +1272,7 @@ class be_class_bytes (scope: global, name: bytes) { .p, var _buffer, func(m_buffer) init, func(m_init) + deinit, func(m_deinit) tostring, func(m_tostring) asstring, func(m_asstring) fromstring, func(m_fromstring) diff --git a/lib/libesp32/Berry/src/be_class.c b/lib/libesp32/Berry/src/be_class.c index b8e3ea2fb..74348fc3f 100644 --- a/lib/libesp32/Berry/src/be_class.c +++ b/lib/libesp32/Berry/src/be_class.c @@ -14,6 +14,7 @@ #include "be_vm.h" #include "be_func.h" #include "be_var.h" +#include #define check_members(vm, c) \ if (!(c)->members) { \ @@ -237,6 +238,25 @@ bbool be_class_newobj(bvm *vm, bclass *c, bvalue *reg, int argc, int mode) return bfalse; } +/* Default empty constructor */ +static int default_init_native_method(bvm *vm) { + be_return_nil(vm); +} + +/* Find instance member by name and copy value to `dst` */ +/* Do not look into virtual members */ +int be_instance_member_simple(bvm *vm, binstance *instance, bstring *name, bvalue *dst) +{ + int type; + be_assert(name != NULL); + binstance * obj = instance_member(vm, instance, name, dst); + type = var_type(dst); + if (obj && type == MT_VARIABLE) { + *dst = obj->members[dst->v.i]; + } + return type; +} + /* Find instance member by name and copy value to `dst` */ /* Input: none of `obj`, `name` and `dst` may not be NULL */ /* Returns the type of the member or BE_NONE if member not found */ @@ -253,22 +273,28 @@ int be_instance_member(bvm *vm, binstance *instance, bstring *name, bvalue *dst) if (obj) { return type; } else { /* if no method found, try virtual */ - /* get method 'member' */ - obj = instance_member(vm, instance, str_literal(vm, "member"), vm->top); - if (obj && basetype(var_type(vm->top)) == BE_FUNCTION) { - bvalue *top = vm->top; - var_setinstance(&top[1], instance); - var_setstr(&top[2], name); - vm->top += 3; /* prevent gc collection results */ - be_dofunc(vm, top, 2); /* call method 'member' */ - vm->top -= 3; - *dst = *vm->top; /* copy result to R(A) */ - if (obj && var_type(dst) == MT_VARIABLE) { - *dst = obj->members[dst->v.i]; - } - type = var_type(dst); - if (type != BE_NIL) { - return type; + /* if 'init' does not exist, create a virtual empty constructor */ + if (strcmp(str(name), "init") == 0) { + var_setntvfunc(dst, default_init_native_method); + return var_type(dst); + } else { + /* get method 'member' */ + obj = instance_member(vm, instance, str_literal(vm, "member"), vm->top); + if (obj && basetype(var_type(vm->top)) == BE_FUNCTION) { + bvalue *top = vm->top; + var_setinstance(&top[1], instance); + var_setstr(&top[2], name); + vm->top += 3; /* prevent gc collection results */ + be_dofunc(vm, top, 2); /* call method 'member' */ + vm->top -= 3; + *dst = *vm->top; /* copy result to R(A) */ + if (obj && var_type(dst) == MT_VARIABLE) { + *dst = obj->members[dst->v.i]; + } + type = var_type(dst); + if (type != BE_NIL) { + return type; + } } } } diff --git a/lib/libesp32/Berry/src/be_class.h b/lib/libesp32/Berry/src/be_class.h index 3186afd66..6a114f53b 100644 --- a/lib/libesp32/Berry/src/be_class.h +++ b/lib/libesp32/Berry/src/be_class.h @@ -59,6 +59,7 @@ void be_closure_method_bind(bvm *vm, bclass *c, bstring *name, bclosure *cl); int be_class_closure_count(bclass *c); void be_class_upvalue_init(bvm *vm, bclass *c); bbool be_class_newobj(bvm *vm, bclass *c, bvalue *argv, int argc, int mode); +int be_instance_member_simple(bvm *vm, binstance *obj, bstring *name, bvalue *dst); int be_instance_member(bvm *vm, binstance *obj, bstring *name, bvalue *dst); int be_class_member(bvm *vm, bclass *obj, bstring *name, bvalue *dst); bbool be_instance_setmember(bvm *vm, binstance *obj, bstring *name, bvalue *src); diff --git a/lib/libesp32/Berry/src/be_debug.c b/lib/libesp32/Berry/src/be_debug.c index 33db01d36..4c6be85a8 100644 --- a/lib/libesp32/Berry/src/be_debug.c +++ b/lib/libesp32/Berry/src/be_debug.c @@ -111,7 +111,7 @@ void be_print_inst(binstruction ins, int pc) logbuf("%s\tK%d", opc2str(op), IGET_Bx(ins)); break; case OP_CLOSE: case OP_LDNIL: - logbuf("%s\t%d", opc2str(op), IGET_RA(ins)); + logbuf("%s\tR%d", opc2str(op), IGET_RA(ins)); break; case OP_RAISE: logbuf("%s\t%d\t%c%d\t%c%d", opc2str(op), IGET_RA(ins), diff --git a/lib/libesp32/Berry/src/be_gc.c b/lib/libesp32/Berry/src/be_gc.c index 8e6ac005e..0475de7b3 100644 --- a/lib/libesp32/Berry/src/be_gc.c +++ b/lib/libesp32/Berry/src/be_gc.c @@ -458,10 +458,13 @@ static void destruct_object(bvm *vm, bgcobject *obj) int type; binstance *ins = cast_instance(obj); /* does not GC when creating the string "deinit". */ - type = be_instance_member(vm, ins, str_literal(vm, "deinit"), vm->top); + type = be_instance_member_simple(vm, ins, str_literal(vm, "deinit"), vm->top); be_incrtop(vm); if (basetype(type) == BE_FUNCTION) { - be_dofunc(vm, vm->top - 1, 1); + var_setinstance(vm->top, ins); /* push instance on stack as arg 1 */ + be_incrtop(vm); + be_dofunc(vm, vm->top - 2, 1); /* warning, there shoudln't be any exception raised here, or the gc stops */ + be_stackpop(vm, 1); } be_stackpop(vm, 1); } diff --git a/lib/libesp32/Berry/src/be_parser.c b/lib/libesp32/Berry/src/be_parser.c index b04b24383..bb455df68 100644 --- a/lib/libesp32/Berry/src/be_parser.c +++ b/lib/libesp32/Berry/src/be_parser.c @@ -97,7 +97,7 @@ static void match_notoken(bparser *parser, btokentype type) } } -/* check that if the expdesc is a symbol, it is avalid one or raise an exception */ +/* check that if the expdesc is a symbol, it is a valid one or raise an exception */ static void check_symbol(bparser *parser, bexpdesc *e) { if (e->type == ETVOID && e->v.s == NULL) { /* error when token is not a symbol */ @@ -106,7 +106,7 @@ static void check_symbol(bparser *parser, bexpdesc *e) } } -/* check that the value in `e` is valid for a variable, i.e. conatins a value or a valid symbol */ +/* check that the value in `e` is valid for a variable, i.e. contains a value or a valid symbol */ static void check_var(bparser *parser, bexpdesc *e) { check_symbol(parser, e); /* check the token is a symbol */ @@ -792,6 +792,13 @@ static void member_expr(bparser *parser, bexpdesc *e) init_exp(&key, ETSTRING, 0); key.v.s = str; be_code_member(parser->finfo, e, &key); + } else if (next_type(parser) == OptLBK) { + scan_next_token(parser); /* skip '(' */ + bexpdesc key; + expr(parser, &key); + check_var(parser, &key); + match_token(parser, OptRBK); /* skip ')' */ + be_code_member(parser->finfo, e, &key); } else { push_error(parser, "invalid syntax near '%s'", be_token2str(parser->vm, &next_token(parser))); @@ -989,6 +996,7 @@ static void cond_expr(bparser *parser, bexpdesc *e) if (next_type(parser) == OptQuestion) { int jf, jl = NO_JUMP; /* jump list */ bfuncinfo *finfo = parser->finfo; + check_var(parser, e); /* check if valid */ scan_next_token(parser); /* skip '?' */ be_code_jumpbool(finfo, e, bfalse); /* go if true */ jf = e->f; diff --git a/lib/libesp32/Berry/src/be_vm.c b/lib/libesp32/Berry/src/be_vm.c index 49e20517f..70857ef72 100644 --- a/lib/libesp32/Berry/src/be_vm.c +++ b/lib/libesp32/Berry/src/be_vm.c @@ -267,7 +267,7 @@ bbool be_value2bool(bvm *vm, bvalue *v) static void obj_method(bvm *vm, bvalue *o, bstring *attr, bvalue *dst) { binstance *obj = var_toobj(o); - int type = be_instance_member(vm, obj, attr, dst); + int type = be_instance_member_simple(vm, obj, attr, dst); if (basetype(type) != BE_FUNCTION) { vm_error(vm, "attribute_error", "the '%s' object has no method '%s'", diff --git a/lib/libesp32/Berry/tests/member_indirect.be b/lib/libesp32/Berry/tests/member_indirect.be new file mode 100644 index 000000000..046521acd --- /dev/null +++ b/lib/libesp32/Berry/tests/member_indirect.be @@ -0,0 +1,75 @@ +#- new syntax for indirect members -# + +#- module accessor -# +s_pi = 'pi' + +import math +assert(math.('pi') == math.pi) +assert(math.(s_pi) == math.pi) + +#- module writer -# +m = module("m") + +m.('aa') = 1 +m.('a' + 'b') = 2 +s_ac = 'ac' +m.(s_ac) = 3 +assert(m.aa == 1) +assert(m.ab == 2) +assert(m.ac == 3) +assert(m.('a'+'a') == 1) + +#- class accessor -# +class A1 + static a = 1, b = 2 + static s = "foo" + def f() return 0 end +end +assert(A1.a == 1) +assert(A1.b == 2) +assert(A1.s == "foo") +assert(type(A1.f) == 'function') + +#- instance accessor -# +class A2 + var a, b + static s_a = 'a' + def init(a,b) + self.(self.('s_a')) = a + self.('b') = b + end + def f(x) + return x+1 + end + def g(a,b) + return A2(a,b) + end +end +a = A2(1,2) + +#- reading members -# +assert(a.a == 1) +assert(a.b == 2) +assert(a.(A2.s_a) == 1) +assert(a.('b') == 2) + +#- writing members -# +a.('a') = 10 +a.('bb'[0]) = 11 +assert(a.a == 10) +assert(a.b == 11) + +#- calling methods -# +assert(a.f(1) == 2) +assert(a.('f')(2) == 3) + +#- mulit-level -# +assert(a.('g')(3,4).('a') == 3) +a.('a') = a.g(3,4) +assert(a.a.b == 4) +assert(a.('a').b == 4) +assert(a.('a').('b') == 4) +assert(a.a.('b') == 4) + +a.('a').('b') += 1 +assert(a.a.b == 5) diff --git a/lib/libesp32/Berry/tests/super_auto.be b/lib/libesp32/Berry/tests/super_auto.be new file mode 100644 index 000000000..8d7ede85f --- /dev/null +++ b/lib/libesp32/Berry/tests/super_auto.be @@ -0,0 +1,132 @@ +#- test for new auto class inference of super() -# + +#- test that we can call init() even if it's not defined -# +class Z end +z=Z() +assert(z.init != nil) +z.init() #- should do nothing -# + +#- check the old way still works -# +class A1 + var a + def init(a) + self.a = a + end +end +class B1:A1 + var b + def init(a,b) + super(self,A1).init(a) + self.b = b + end +end +class C1:B1 + var c + def init(a,b,c) + super(self,B1).init(a,b) + self.c = c + end +end +#- -# +c1=C1(1,2,3) +assert(c1.a == 1) +assert(c1.b == 2) +assert(c1.c == 3) + +#- test simple behavior -# +class A0 var a end +class B0:A0 var b end +class C0:B0 end +c0=C0() +assert(classof(c0) == C0) +assert(classof(super(c0)) == B0) +assert(classof(super(super(c0))) == A0) +assert(super(super(super(c0))) == nil) + +assert(super(C0) == B0) +assert(super(super(C0)) == A0) +assert(super(super(super(C0))) == nil) + +assert(classof(super(c0,B0)) == B0) +assert(classof(super(c0,A0)) == A0) + +#- test auto inference of target superclass -# +class A + var a + def init(a) + self.a = a + end +end +class B:A + var b + def init(a,b) + super(self).init(a) + self.b = b + end +end +class C:B + var c + def init(a,b,c) + super(self).init(a,b) + self.c = c + end +end +#- -# +c=C(1,2,3) + +assert(c.a == 1) +assert(c.b == 2) +assert(c.c == 3)class A +end +class B:A + var b + def init(a,b) super(self).init(a) self.b = b end +end +class C:B + var c + def init(a,b,c) super(self).init(a,b) self.c = c end +end +c=C(1,2,3) + +#- variant if A2 does not have an init() method, still works -# +class A2 + static a=1 +end +class B2:A2 + var b + def init(a,b) super(self).init(a) self.b = b end +end +class C2:B2 + var c + def init(a,b,c) super(self).init(a,b) self.c = c end +end +#- -# +c2=C2(1,2,3) +assert(c2.a == 1) +assert(c2.b == 2) +assert(c2.c == 3) + +#- difference in behavior whether the second arg is provided or not -# +class A3 +end +class B3:A3 + def b1() + return super(self) + end + def b2(c) + return super(self, c) + end +end +class C3:B3 +end +#- -# +b3=B3() +c3=C3() +assert(classof(c3.b1()) == A3) +assert(classof(b3.b1()) == A3) +assert(classof(c3.b2(B3)) == B3) +assert(classof(c3.b2(A3)) == A3) + +assert(classof(c3.b2(nil)) == B3) #- testing super(self,nil) in B3::b2() -# + +assert(c3.b2(C3) == nil) #- if specifying the current class, can't find any relevant class in supers -# \ No newline at end of file diff --git a/lib/libesp32/Berry/tests/super_leveled.be b/lib/libesp32/Berry/tests/super_leveled.be index 09b7a115b..e88914941 100644 --- a/lib/libesp32/Berry/tests/super_leveled.be +++ b/lib/libesp32/Berry/tests/super_leveled.be @@ -26,10 +26,9 @@ assert(classname(super(super(C))) == 'A') assert(super(super(super(C))) == nil) #- super() levele -# -assert(super(a,A) == a) -assert(classname(super(a,A)) == 'A') -assert(classname(super(b,B)) == 'B') -assert(classname(super(c,C)) == 'C') +assert(super(a,A) == nil) +assert(super(b,B) == nil) +assert(super(c,C) == nil) assert(classname(super(c,B)) == 'B') assert(classname(super(c,A)) == 'A') assert(super(c,map) == nil) #- not a parent class -# diff --git a/tasmota/language/af_AF.h b/tasmota/language/af_AF.h index 9cf907e22..766f31d6a 100644 --- a/tasmota/language/af_AF.h +++ b/tasmota/language/af_AF.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/bg_BG.h b/tasmota/language/bg_BG.h index 59af27f2c..358390bc9 100644 --- a/tasmota/language/bg_BG.h +++ b/tasmota/language/bg_BG.h @@ -835,6 +835,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/cs_CZ.h b/tasmota/language/cs_CZ.h index 6bffd8849..be7396a92 100644 --- a/tasmota/language/cs_CZ.h +++ b/tasmota/language/cs_CZ.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index d0fccd6ca..2e94554d1 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -28,7 +28,7 @@ * Use online command StateText to translate ON, OFF, HOLD and TOGGLE. * Use online command Prefix to translate cmnd, stat and tele. * - * Updated until v9.5.0.3 + * Updated until v9.5.0.7 \*********************************************************************/ //#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English) @@ -234,8 +234,8 @@ #define D_SYSLOG_HOST_NOT_FOUND "Syslog-Host nicht gefunden" // settings.ino -#define D_SAVED_TO_FLASH_AT "in Flash gespeichert am" -#define D_LOADED_FROM_FLASH_AT "aus Flash geladen am" +#define D_SAVED_TO_FLASH_AT "in Flash gespeichert an" +#define D_LOADED_FROM_FLASH_AT "aus Flash geladen von" #define D_USE_DEFAULTS "Standard verwenden" #define D_ERASED_SECTOR "gelöschter Sektor" @@ -330,7 +330,7 @@ #define D_MQTT_TLS_ENABLE "MQTT TLS" #define D_HTTP_API "HTTP API" #define D_HTTP_API_ENABLE "HTTP API aktivieren" -#define D_FRIENDLY_NAME "Name [friendly name]" +#define D_FRIENDLY_NAME "Name [Friendly Name]" #define D_BELKIN_WEMO "Belkin WeMo" #define D_HUE_BRIDGE "Hue Bridge" #define D_SINGLE_DEVICE "Einzelnes Gerät" @@ -493,19 +493,19 @@ #define D_ZIGBEE_UNKNWON_ATTRIBUTE "Unbekannter Attribut Name (ignoriert): %s" #define D_ZIGBEE_TOO_MANY_CLUSTERS "Nur eine Cluster id pro Kommando" #define D_ZIGBEE_WRONG_DELIMITER "Falscher Delimeter für Payload" -#define D_ZIGBEE_UNRECOGNIZED_COMMAND "Unerkanntes zigbee Kommando: %s" +#define D_ZIGBEE_UNRECOGNIZED_COMMAND "Unerkanntes Zigbee Kommando: %s" #define D_ZIGBEE_TOO_MANY_COMMANDS "Nur 1 Kommando zulässig (%d)" #define D_ZIGBEE_NO_ATTRIBUTE "Kein Attribut in der Liste" #define D_ZIGBEE_UNSUPPORTED_ATTRIBUTE_TYPE "Nicht unterstützter Attribut Typ" #define D_ZIGBEE_JSON_REQUIRED "Konfiguration muss JSON basiert sein" #define D_ZIGBEE_RESET_1_OR_2 "1 oder 2 für Reset" -#define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "ZBBridge EEPROM gefunden auf Adresse" +#define D_ZIGBEE_EEPROM_FOUND_AT_ADDRESS "ZBBridge EEPROM gefunden an Adresse" #define D_ZIGBEE_RANDOMIZING_ZBCONFIG "Zufällige Zigbee Parameter erstellt, Überprüfung mit 'ZbConfig'" // xdrv_03_energy.ino #define D_ENERGY_TODAY "Energie heute" #define D_ENERGY_YESTERDAY "Energie gestern" -#define D_ENERGY_TOTAL "Energie insgesamt" +#define D_ENERGY_TOTAL "Energie gesamt" // xdrv_27_shutter.ino #define D_OPEN "Öffnen" @@ -533,7 +533,7 @@ #define D_CHECKSUM_FAILURE "Prüfsummen-Fehler" // xsns_07_sht1x.ino -#define D_SENSOR_DID_NOT_ACK_COMMAND "Sensor hat ACK-Befehl nicht ausgeführt" +#define D_SENSOR_DID_NOT_ACK_COMMAND "Sensor hat Befehl nicht ausgeführt" #define D_SHT1X_FOUND "SHT1X gefunden" // xsns_18_pms5003.ino @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" @@ -942,9 +945,9 @@ #define D_FS_SIZE "Größe" #define D_FS_FREE "Frei" #define D_NEW_FILE "neue-datei.txt" -#define D_CREATE_NEW_FILE "Neue Datei erstellen und bearbeiten" +#define D_CREATE_NEW_FILE "Datei erstellen und bearbeiten" #define D_EDIT_FILE "Datei bearbeiten" -#define D_CONFIRM_FILE_DEL "Löschen der Datei bestätigen" +#define D_CONFIRM_FILE_DEL "Datei löschen bestätigen" //xsns_67_as3935.ino #define D_AS3935_GAIN "Umgebung:" @@ -1014,7 +1017,7 @@ #define D_FP_INVALIDIMAGE "Abbild ungültig" // 0x15 Failed to generate image because of lac of valid primary image #define D_FP_FLASHERR "Flash Schreibfehler" // 0x18 Error when writing flash #define D_FP_INVALIDREG "Ungültige ID-Nummer" // 0x1A Invalid register number -#define D_FP_ADDRCODE "Addresse" // 0x20 Address code +#define D_FP_ADDRCODE "Adresse" // 0x20 Address code #define D_FP_PASSVERIFY "Übereinstimmung" // 0x21 Verify the fingerprint passed #define D_FP_UNKNOWNERROR "Fehler" // Any other error diff --git a/tasmota/language/el_GR.h b/tasmota/language/el_GR.h index e71ee964a..d18aa9bce 100644 --- a/tasmota/language/el_GR.h +++ b/tasmota/language/el_GR.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/en_GB.h b/tasmota/language/en_GB.h index 40986f104..2caab6abc 100644 --- a/tasmota/language/en_GB.h +++ b/tasmota/language/en_GB.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/es_ES.h b/tasmota/language/es_ES.h index 239cb2ac7..c5d98d79e 100644 --- a/tasmota/language/es_ES.h +++ b/tasmota/language/es_ES.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fr_FR.h b/tasmota/language/fr_FR.h index a63b252c0..bd63fc53f 100644 --- a/tasmota/language/fr_FR.h +++ b/tasmota/language/fr_FR.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fy_NL.h b/tasmota/language/fy_NL.h index 3984dc0c5..86e0fb7d2 100644 --- a/tasmota/language/fy_NL.h +++ b/tasmota/language/fy_NL.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/he_HE.h b/tasmota/language/he_HE.h index eba8fa655..efd02b2f4 100644 --- a/tasmota/language/he_HE.h +++ b/tasmota/language/he_HE.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/hu_HU.h b/tasmota/language/hu_HU.h index f7d51de3e..3da61ccc6 100644 --- a/tasmota/language/hu_HU.h +++ b/tasmota/language/hu_HU.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index 8d76ab9d3..2d80dea18 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "Effetto hall" #define D_SENSOR_EPD_DATA "EPD - Dati" #define D_SENSOR_MCP2515_CS "MCP2515 - CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ko_KO.h b/tasmota/language/ko_KO.h index 5da66f819..d427966cd 100644 --- a/tasmota/language/ko_KO.h +++ b/tasmota/language/ko_KO.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/nl_NL.h b/tasmota/language/nl_NL.h index 54ed3cc5b..56015b023 100644 --- a/tasmota/language/nl_NL.h +++ b/tasmota/language/nl_NL.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pl_PL.h b/tasmota/language/pl_PL.h index d90d6099a..7f9b79727 100644 --- a/tasmota/language/pl_PL.h +++ b/tasmota/language/pl_PL.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "Efekt Halla" #define D_SENSOR_EPD_DATA "EPD Dane" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_BR.h b/tasmota/language/pt_BR.h index e7b0a938f..83a9d88c3 100644 --- a/tasmota/language/pt_BR.h +++ b/tasmota/language/pt_BR.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "Efeito Hall" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt_PT.h b/tasmota/language/pt_PT.h index 4eeb9eb28..831563664 100644 --- a/tasmota/language/pt_PT.h +++ b/tasmota/language/pt_PT.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "Efeito Hall" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ro_RO.h b/tasmota/language/ro_RO.h index e3ec62390..b1058e60d 100644 --- a/tasmota/language/ro_RO.h +++ b/tasmota/language/ro_RO.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ru_RU.h b/tasmota/language/ru_RU.h index 9f58134c1..b6fb1bc35 100644 --- a/tasmota/language/ru_RU.h +++ b/tasmota/language/ru_RU.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/sk_SK.h b/tasmota/language/sk_SK.h index ac554d43e..3ac233b00 100644 --- a/tasmota/language/sk_SK.h +++ b/tasmota/language/sk_SK.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/sv_SE.h b/tasmota/language/sv_SE.h index 5d46cb0f5..c026d53b6 100644 --- a/tasmota/language/sv_SE.h +++ b/tasmota/language/sv_SE.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/tr_TR.h b/tasmota/language/tr_TR.h index bfdd34f63..5d2d969b4 100644 --- a/tasmota/language/tr_TR.h +++ b/tasmota/language/tr_TR.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/uk_UA.h b/tasmota/language/uk_UA.h index 25ffd199c..3be6240c2 100644 --- a/tasmota/language/uk_UA.h +++ b/tasmota/language/uk_UA.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/vi_VN.h b/tasmota/language/vi_VN.h index 59efb8330..d1e855bec 100644 --- a/tasmota/language/vi_VN.h +++ b/tasmota/language/vi_VN.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/zh_CN.h b/tasmota/language/zh_CN.h index 036f4c352..c9a64adac 100644 --- a/tasmota/language/zh_CN.h +++ b/tasmota/language/zh_CN.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/zh_TW.h b/tasmota/language/zh_TW.h index 18540574f..2074f04f7 100644 --- a/tasmota/language/zh_TW.h +++ b/tasmota/language/zh_TW.h @@ -836,6 +836,9 @@ #define D_SENSOR_HALLEFFECT "HallEffect" #define D_SENSOR_EPD_DATA "EPD Data" #define D_SENSOR_MCP2515_CS "MCP2515 CS" +#define D_SENSOR_HRG15_RX "HRG15 Rx" +#define D_SENSOR_HRG15_TX "HRG15 Tx" +#define D_SENSOR_VINDRIKTNING_RX "VINDRIKTNING" // Units #define D_UNIT_AMPERE "安培" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 1dbb347cb..9c85c7549 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -743,6 +743,8 @@ //#define USE_AS608 // Add support for AS608 optical and R503 capacitive fingerprint sensor (+3k code) // #define USE_AS608_MESSAGES // Add verbose error messages (+0k4 code) //#define USE_TFMINIPLUS // Add support for TFmini Plus (TFmini, TFmini-S) LiDAR modules via UART interface (+0k8) +//#define USE_HRG15 // Add support for Hydreon RG-15 Solid State Rain sensor (+1k5 code) +//#define USE_VINDRIKTNING // Add support for IKEA VINDRIKTNING particle concentration sensor (+1k code) // -- Power monitoring sensors -------------------- #define USE_ENERGY_SENSOR // Add support for Energy Monitors (+14k code) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index ec5bc7bdd..114809a00 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -762,9 +762,12 @@ void ResponseAppendFeatures(void) #ifdef USE_INFLUXDB feature8 |= 0x00000800; // xdrv_59_influxdb.ino #endif - -// feature8 |= 0x00001000; -// feature8 |= 0x00002000; +#ifdef USE_HRG15 + feature8 |= 0x00001000; // xsns_90_hrg15.ino +#endif +#ifdef USE_VINDRIKTNING + feature8 |= 0x00002000; // xsns_91_vindriktning.ino +#endif // feature8 |= 0x00004000; // feature8 |= 0x00008000; diff --git a/tasmota/support_switch.ino b/tasmota/support_switch.ino index 852cb595a..4b1688c72 100644 --- a/tasmota/support_switch.ino +++ b/tasmota/support_switch.ino @@ -237,20 +237,32 @@ void SwitchHandler(uint32_t mode) { uint32_t mqtt_action = POWER_NONE; uint32_t switchmode = Settings->switchmode[i]; - if (Switch.hold_timer[i] & (((switchmode == PUSHHOLDMULTI) | (switchmode == PUSHHOLDMULTI_INV)) ? SM_TIMER_MASK: SM_NO_TIMER_MASK)) { + bool push_hold_multi_delay = ((PUSHHOLDMULTIDELAY == switchmode) || (PUSHHOLDMULTIDELAY_INV == switchmode)); + if (push_hold_multi_delay) { + switchmode -= (PUSHHOLDMULTIDELAY - PUSHHOLDMULTI); + } + bool push_hold_multi = ((PUSHHOLDMULTI == switchmode) || (PUSHHOLDMULTI_INV == switchmode)); + + if (Switch.hold_timer[i] & ((push_hold_multi) ? SM_TIMER_MASK : SM_NO_TIMER_MASK)) { Switch.hold_timer[i]--; if ((Switch.hold_timer[i] & SM_TIMER_MASK) == loops_per_second * Settings->param[P_HOLD_TIME] / 25) { - if ((switchmode == PUSHHOLDMULTI) | (switchmode == PUSHHOLDMULTI_INV)){ - if (((switchmode == PUSHHOLDMULTI) & (NOT_PRESSED == Switch.last_state[i])) | ((switchmode == PUSHHOLDMULTI_INV) & (PRESSED == Switch.last_state[i]))) { - SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); // Execute command via MQTT - } - else if ((Settings->flag5.switch_dimmer_act_at_rls) & ((Switch.hold_timer[i] & ~SM_TIMER_MASK) == SM_FIRST_PRESS)) { - switchflag = POWER_TOGGLE; // Toggle with pushbutton - Switch.hold_timer[i] = 0; - } + bool do_sendkey = false; + switch (switchmode) { + case PUSHHOLDMULTI: + do_sendkey = (NOT_PRESSED == Switch.last_state[i]); + break; + case PUSHHOLDMULTI_INV: + do_sendkey = (PRESSED == Switch.last_state[i]); + break; + } + if (do_sendkey) { + SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); // Execute command via MQTT + } else if (push_hold_multi_delay && ((Switch.hold_timer[i] & ~SM_TIMER_MASK) == SM_FIRST_PRESS)) { + switchflag = POWER_TOGGLE; // Toggle with pushbutton + Switch.hold_timer[i] = 0; } } - if (0 == (Switch.hold_timer[i] & (((switchmode == PUSHHOLDMULTI) | (switchmode == PUSHHOLDMULTI_INV)) ? SM_TIMER_MASK: SM_NO_TIMER_MASK))) { + if (0 == (Switch.hold_timer[i] & ((push_hold_multi) ? SM_TIMER_MASK: SM_NO_TIMER_MASK))) { switch (switchmode) { case TOGGLEMULTI: switchflag = POWER_TOGGLE; // Toggle after hold @@ -277,7 +289,6 @@ void SwitchHandler(uint32_t mode) { Switch.hold_timer[i] = loops_per_second * Settings->param[P_HOLD_TIME] / 25; SendKey(KEY_SWITCH, i +1, POWER_INCREMENT); // Execute command via MQTT mqtt_action = POWER_INCREMENT; - } else { Switch.hold_timer[i]= 0; SendKey(KEY_SWITCH, i +1, POWER_CLEAR); // Execute command via MQTT @@ -352,10 +363,10 @@ void SwitchHandler(uint32_t mode) { } } else { if ((Switch.hold_timer[i] & SM_TIMER_MASK) > loops_per_second * Settings->param[P_HOLD_TIME] / 25) { - if((Switch.hold_timer[i] & ~SM_TIMER_MASK) != SM_SECOND_PRESS) { + if ((Switch.hold_timer[i] & ~SM_TIMER_MASK) != SM_SECOND_PRESS) { Switch.hold_timer[i]= SM_FIRST_PRESS; - if (!Settings->flag5.switch_dimmer_act_at_rls){ - switchflag = POWER_TOGGLE; // Toggle with pushbutton + if (!push_hold_multi_delay) { + switchflag = POWER_TOGGLE; // Toggle with pushbutton } } else{ @@ -380,10 +391,10 @@ void SwitchHandler(uint32_t mode) { } } else { if ((Switch.hold_timer[i] & SM_TIMER_MASK)> loops_per_second * Settings->param[P_HOLD_TIME] / 25) { - if((Switch.hold_timer[i] & ~SM_TIMER_MASK) != SM_SECOND_PRESS) { + if ((Switch.hold_timer[i] & ~SM_TIMER_MASK) != SM_SECOND_PRESS) { Switch.hold_timer[i]= SM_FIRST_PRESS; - if (!Settings->flag5.switch_dimmer_act_at_rls){ - switchflag = POWER_TOGGLE; // Toggle with pushbutton + if (!push_hold_multi_delay) { + switchflag = POWER_TOGGLE; // Toggle with pushbutton } } else{ diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 5a475cc9d..19f485329 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -280,7 +280,8 @@ enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_D enum WifiConfigOptions {WIFI_RESTART, EX_WIFI_SMARTCONFIG, WIFI_MANAGER, EX_WIFI_WPSCONFIG, WIFI_RETRY, WIFI_WAIT, WIFI_SERIAL, WIFI_MANAGER_RESET_ONLY, MAX_WIFI_OPTION}; enum SwitchModeOptions {TOGGLE, FOLLOW, FOLLOW_INV, PUSHBUTTON, PUSHBUTTON_INV, PUSHBUTTONHOLD, PUSHBUTTONHOLD_INV, PUSHBUTTON_TOGGLE, TOGGLEMULTI, - FOLLOWMULTI, FOLLOWMULTI_INV, PUSHHOLDMULTI, PUSHHOLDMULTI_INV, PUSHON, PUSHON_INV, PUSH_IGNORE, MAX_SWITCH_OPTION}; + FOLLOWMULTI, FOLLOWMULTI_INV, PUSHHOLDMULTI, PUSHHOLDMULTI_INV, PUSHON, PUSHON_INV, PUSH_IGNORE, PUSHNOTUSED, PUSHHOLDMULTIDELAY, + PUSHHOLDMULTIDELAY_INV, MAX_SWITCH_OPTION}; enum LedStateOptions {LED_OFF, LED_POWER, LED_MQTTSUB, LED_POWER_MQTTSUB, LED_MQTTPUB, LED_POWER_MQTTPUB, LED_MQTT, LED_POWER_MQTT, MAX_LED_OPTION}; diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index 6530cdbc3..bdcb005cc 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -188,6 +188,7 @@ // #define USE_PROJECTOR_CTRL_OPTOMA // Use codes for OPTOMA //#define USE_AS608 // Add support for AS608 optical and R503 capacitive fingerprint sensor (+3k4 code) //#define USE_TFMINIPLUS // Add suppoer for TFmini Plus (TFmini, TFmini-S) LiDAR modules via UART interface +//#define USE_HRG15 // Add support for Hydreon RG-15 Solid State Rain sensor (+1k5 code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) diff --git a/tasmota/tasmota_configurations_ESP32.h b/tasmota/tasmota_configurations_ESP32.h index 5f944082d..c25f42de9 100644 --- a/tasmota/tasmota_configurations_ESP32.h +++ b/tasmota/tasmota_configurations_ESP32.h @@ -476,6 +476,7 @@ // #define USE_PROJECTOR_CTRL_OPTOMA // Use codes for OPTOMA //#define USE_AS608 // Add support for AS608 optical and R503 capacitive fingerprint sensor (+3k4 code) //#define USE_TFMINIPLUS // Add support for TFmini Plus (TFmini, TFmini-S) LiDAR modules via UART interface +//#define USE_HRG15 // Add support for Hydreon RG-15 Solid State Rain sensor (+1k5 code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index fb7220586..cb8b664af 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -170,6 +170,8 @@ enum UserSelectablePins { GPIO_I2S_IN_DATA, GPIO_I2S_IN_CLK, GPIO_I2S_IN_SLCT, GPIO_INTERRUPT, GPIO_MCP2515_CS, // MCP2515 Chip Select + GPIO_HRG15_TX, GPIO_HRG15_RX, // Hydreon RG-15 rain sensor serial interface + GPIO_VINDRIKTNING_RX, // IKEA VINDRIKTNING Serial interface GPIO_SENSOR_END }; enum ProgramSelectablePins { @@ -360,6 +362,8 @@ const char kSensorNames[] PROGMEM = D_SENSOR_I2S_IN_DATA "|" D_SENSOR_I2S_IN_CLK "|" D_SENSOR_I2S_IN_SLCT "|" D_SENSOR_INTERRUPT "|" D_SENSOR_MCP2515_CS "|" + D_SENSOR_HRG15_TX "|" D_SENSOR_HRG15_RX "|" + D_SENSOR_VINDRIKTNING_RX ; const char kSensorNamesFixed[] PROGMEM = @@ -791,6 +795,13 @@ const uint16_t kGpioNiceList[] PROGMEM = { AGPIO(GPIO_AS608_TX), AGPIO(GPIO_AS608_RX), #endif +#ifdef USE_HRG15 + AGPIO(GPIO_HRG15_TX), + AGPIO(GPIO_HRG15_RX), +#endif +#ifdef USE_VINDRIKTNING + AGPIO(GPIO_VINDRIKTNING_RX), +#endif /*-------------------------------------------------------------------------------------------*\ * Other sensors diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index a4d454040..9e2652d06 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -1171,7 +1171,11 @@ void CmndSubscribe(void) subscription_item.Key = key; subscriptions.add(subscription_item); + if (2 == XdrvMailbox.index) { + topic = subscription_item.Topic; // Do not append "/#"" + } MqttSubscribe(topic.c_str()); + events.concat(event_name + "," + topic + (key.length()>0 ? "," : "") + key); diff --git a/tasmota/xlgt_05_sonoff_l1.ino b/tasmota/xlgt_05_sonoff_l1.ino index f120bbd79..6be1205f2 100644 --- a/tasmota/xlgt_05_sonoff_l1.ino +++ b/tasmota/xlgt_05_sonoff_l1.ino @@ -182,6 +182,11 @@ bool SnfL1SerialInput(void) { snprintf_P(cmnd_dimmer, sizeof(cmnd_dimmer), PSTR(D_CMND_DIMMER " %d"), dimmer); } + else if (!strncmp(token2, "\"mode\"", 6)) { + uint8_t received_mode = atoi(token3); + Settings->sbflag1.sonoff_l1_music_sync = (SONOFF_L1_MODE_SYNC_TO_MUSIC == received_mode); + } + token = strtok_r(nullptr, ",", &end_str); } @@ -250,19 +255,19 @@ bool SnfL1SetChannels(void) { } if (!power_changed && !dimmer_changed && !color_changed && (Snfl1.old_music_sync == Settings->sbflag1.sonoff_l1_music_sync)) { return true; } - uint32_t mode = SONOFF_L1_MODE_COLORFUL; - if (Settings->sbflag1.sonoff_l1_music_sync) { - mode = SONOFF_L1_MODE_SYNC_TO_MUSIC; - } - - snprintf_P(Snfl1.buffer, SONOFF_L1_BUFFER_SIZE, PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d,\"sensitive\":%d,\"speed\":%d"), + uint32_t mode = (Settings->sbflag1.sonoff_l1_music_sync) ? SONOFF_L1_MODE_SYNC_TO_MUSIC : SONOFF_L1_MODE_COLORFUL; + snprintf_P(Snfl1.buffer, SONOFF_L1_BUFFER_SIZE, PSTR("AT+UPDATE=\"sequence\":\"%d%03d\",\"switch\":\"%s\",\"light_type\":1,\"colorR\":%d,\"colorG\":%d,\"colorB\":%d,\"bright\":%d,\"mode\":%d"), LocalTime(), millis()%1000, Snfl1.power ? "on" : "off", Snfl1.color[0], Snfl1.color[1], Snfl1.color[2], Snfl1.dimmer, - mode, - Snfl1.sensitive, - Snfl1.speed); + mode); + if (SONOFF_L1_MODE_SYNC_TO_MUSIC == mode) { + snprintf_P(Snfl1.buffer, SONOFF_L1_BUFFER_SIZE, PSTR("%s,\"sensitive\":%d,\"speed\":%d"), + Snfl1.buffer, + Snfl1.sensitive, + Snfl1.speed); + } #ifdef SONOFF_L1_START_DELAY static bool first_call = true; @@ -281,6 +286,16 @@ bool SnfL1SetChannels(void) { return true; } +bool SnfL1SetChannelsFromFunc(void) { + static bool first_call = true; + if (first_call) { + first_call = false; // Allow MusicSync at init time + } else { + Settings->sbflag1.sonoff_l1_music_sync = 0; // Disable MusicSync on user color change + } + return SnfL1SetChannels(); +} + bool SnfL1ModuleSelected(void) { if (SONOFF_L1 == TasmotaGlobal.module_type) { if (PinUsed(GPIO_RXD) && PinUsed(GPIO_TXD)) { @@ -340,7 +355,7 @@ bool Xlgt05(uint8_t function) result = SnfL1SerialInput(); break; case FUNC_SET_CHANNELS: - result = SnfL1SetChannels(); + result = SnfL1SetChannelsFromFunc(); break; case FUNC_MODULE_INIT: result = SnfL1ModuleSelected(); diff --git a/tasmota/xsns_18_pms5003.ino b/tasmota/xsns_18_pms5003.ino index 604583067..b1cac1a81 100644 --- a/tasmota/xsns_18_pms5003.ino +++ b/tasmota/xsns_18_pms5003.ino @@ -48,6 +48,7 @@ struct PMS5003 { uint8_t valid = 0; uint8_t wake_mode = 1; uint8_t ready = 1; + bool discovery_triggered = false; } Pms; enum PmsCommands @@ -156,6 +157,11 @@ bool PmsReadData(void) #endif // PMS_MODEL_PMS3003 Pms.valid = 10; + if (!Pms.discovery_triggered) { + TasmotaGlobal.discovery_counter = 1; // Force discovery + Pms.discovery_triggered = true; + } + return true; } diff --git a/tasmota/xsns_90_hrg15.ino b/tasmota/xsns_90_hrg15.ino new file mode 100644 index 000000000..f7ba63721 --- /dev/null +++ b/tasmota/xsns_90_hrg15.ino @@ -0,0 +1,233 @@ +/* + xsns_90-hrg15.ino - Hydreon RG-15 support for Tasmota + + Copyright (c) 2021 Wouter Breukink + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_HRG15 +/*********************************************************************************************\ + * Hydreon RG-15 + * See https://rainsensors.com/products/rg-15/ +\*********************************************************************************************/ + +#define XSNS_90 90 + +#define RG15_NAME "RG-15" +#define RG15_BAUDRATE 9600 +#define RG15_READ_TIMEOUT 500 +#define RG15_EVENT_TIMEOUT 60 + +#include + +#ifdef USE_WEBSERVER +const char HTTP_RG15[] PROGMEM = + // {s} = , {m} = , {e} = + "{s}" RG15_NAME " " D_JSON_ACTIVE "{m}%2_f " D_UNIT_MILLIMETER "{e}" + "{s}" RG15_NAME " " D_JSON_EVENT "{m}%2_f " D_UNIT_MILLIMETER "{e}" + "{s}" RG15_NAME " " D_JSON_TOTAL "{m}%2_f " D_UNIT_MILLIMETER "{e}" + "{s}" RG15_NAME " " D_JSON_FLOWRATE "{m}%2_f " D_UNIT_MILLIMETER "/" D_UNIT_HOUR "{e}"; +#endif // USE_WEBSERVER + +TasmotaSerial *HydreonSerial; + +struct RG15 { + uint16_t time = RG15_EVENT_TIMEOUT; + uint8_t ready = 1; + uint8_t received = 0; + float acc = 0.0f; + float event = 0.0f; + float total = 0.0f; + float rate = 0.0f; +} Rg15; + +void Rg15Init(void) +{ + Rg15.ready = 0; + if (PinUsed(GPIO_HRG15_RX) && PinUsed(GPIO_HRG15_TX)) { + HydreonSerial = new TasmotaSerial(Pin(GPIO_HRG15_RX), Pin(GPIO_HRG15_TX)); + if (HydreonSerial->begin(RG15_BAUDRATE)) { + if (HydreonSerial->hardwareSerial()) { ClaimSerial(); } + + HydreonSerial->println('R'); + + Rg15.ready = 1; + } + } +} + +bool Rg15Poll(void) { + + // Trigger the first update + if (! Rg15.received) { + HydreonSerial->println('R'); + } + + if (! HydreonSerial->available()) { + + // Check if the rain event has timed out, reset rate to 0 + if (++Rg15.time == RG15_EVENT_TIMEOUT) { + Rg15.acc = 0; + Rg15.rate = 0; + MqttPublishSensor(); + } + + return false; + } + + // Now read what's available + char rg15_buffer[255]; + + while (HydreonSerial->available()) { + Rg15ReadLine(rg15_buffer); + AddLog(LOG_LEVEL_DEBUG_MORE,PSTR("%s:" D_JSON_SERIALRECEIVED " = %s"),"HRG", rg15_buffer); + + Rg15Process(rg15_buffer); + } + + MqttPublishSensor(); + + return true; +} + +bool Rg15ReadLine(char* buffer) +{ + char c; + uint8_t i = 0; + uint32_t cmillis = millis(); + + while (1) { + if (HydreonSerial->available()) { + c = HydreonSerial->read(); + buffer[i++] = c; + + if (c == 10) { break; } // New line ends the message + if (i == 254) { break; } // Overflow + } + + if ((millis() - cmillis) > RG15_READ_TIMEOUT) { + return false; + } + } + + buffer[i-2] = '\0'; + return true; +} + +void Rg15Process(char* buffer) { + + // Process payload, example: Acc 0.01 mm, EventAcc 2.07 mm, TotalAcc 54.85 mm, RInt 2.89 mmph + Rg15.received = 1; + Rg15.acc = Rg15Parse(buffer, "Acc"); + Rg15.event = Rg15Parse(buffer, "EventAcc"); + Rg15.total = Rg15Parse(buffer, "TotalAcc"); + Rg15.rate = Rg15Parse(buffer, "RInt"); + + if (Rg15.acc > 0.0f) { + Rg15.time = 0; // We have some data, so the rain event is on-going + } +} + +float Rg15Parse(char* buffer, const char* item) { + char* start = strstr(buffer, item); + if (start != nullptr) { + char* end = strstr(start, " mm"); + char tmp = end[0]; + end[0] = '\0'; + float result = CharToFloat (start + strlen(item)); + end[0] = tmp; + return result; + } else { + return 0.0f; + } +} + +bool Rg15Command(void) { + bool serviced = true; + + if (XdrvMailbox.data_len == 1) { + char *send = XdrvMailbox.data; + HydreonSerial->println(send); + HydreonSerial->flush(); + + if (send[0] == 'k' || send[0] == 'K' || send[0] == 'o' || send[0] == 'O') { + ResponseCmndDone(); + return serviced; + } + + char rg15_buffer[255]; + if (Rg15ReadLine(rg15_buffer)) { + Response_P(PSTR("{\"" D_JSON_SERIALRECEIVED "\":%s\"}"), rg15_buffer); + Rg15Process(rg15_buffer); + } + } + + return serviced; +} + +void Rg15Show(bool json) +{ + if (!Rg15.received) { + return; + } + + if (json) { + ResponseAppend_P(PSTR(",\"" RG15_NAME "\":{\"" D_JSON_ACTIVE "\":%2_f, \"" D_JSON_EVENT "\":%2_f, \"" D_JSON_TOTAL "\":%2_f, \"" D_JSON_FLOWRATE "\":%2_f}"), &Rg15.acc, &Rg15.event, &Rg15.total, &Rg15.rate); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_RG15, &Rg15.acc, &Rg15.event, &Rg15.total, &Rg15.rate); +#endif // USE_WEBSERVER + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns90(uint8_t function) +{ + + bool result = false; + + if (Rg15.ready) + { + switch (function) + { + case FUNC_INIT: + Rg15Init(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_90 == XdrvMailbox.index) { + Rg15Command(); + } + break; + case FUNC_EVERY_SECOND: + Rg15Poll(); + break; + case FUNC_JSON_APPEND: + Rg15Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + Rg15Show(0); + break; +#endif // USE_WEBSERVER + } + } + + return result; +} + +#endif // USE_HRG15 \ No newline at end of file diff --git a/tasmota/xsns_91_vindriktning.ino b/tasmota/xsns_91_vindriktning.ino new file mode 100644 index 000000000..640eeede9 --- /dev/null +++ b/tasmota/xsns_91_vindriktning.ino @@ -0,0 +1,170 @@ +/* + xsns_91_vindriktning.ino - IKEA vindriktning particle concentration sensor support for Tasmota + + Copyright (C) 2021 Marcel Ritter and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_VINDRIKTNING +/*********************************************************************************************\ + * IKEA VINDRIKTNING particle concentration sensor +\*********************************************************************************************/ + +#define XSNS_91 91 + +#include + +#ifndef MIN_INTERVAL_PERIOD +#define MIN_INTERVAL_PERIOD 60 // minimum interval period in seconds required for passive mode +#endif + +#define VINDRIKTNING_DATASET_SIZE 20 + +TasmotaSerial *VindriktningSerial; + +struct VINDRIKTNING { + uint16_t pm2_5 = 0; + uint16_t pm1_0 = 0; + uint16_t pm10 = 0; + uint8_t type = 1; + uint8_t valid = 0; + bool discovery_triggered = false; +} Vindriktning; + +bool VindriktningReadData(void) { + if (!VindriktningSerial->available()) { + return false; + } + + int serial_in_byte_counter = 0; + uint8_t buffer[VINDRIKTNING_DATASET_SIZE]; + uint8_t crc = 0; + + while (VindriktningSerial->available()) { + uint8_t serial_in_byte = VindriktningSerial->read(); + if (serial_in_byte_counter <= VINDRIKTNING_DATASET_SIZE -1) { + buffer[serial_in_byte_counter++] = serial_in_byte; + crc += serial_in_byte; + } + } + VindriktningSerial->flush(); // Make room for another burst + + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, buffer, VINDRIKTNING_DATASET_SIZE); + + if (serial_in_byte_counter < VINDRIKTNING_DATASET_SIZE) { + AddLog(LOG_LEVEL_DEBUG, PSTR("VDN: Not enough data (%d < 20)"), serial_in_byte_counter); + return false; + } + + if (crc != 0) { + AddLog(LOG_LEVEL_DEBUG, PSTR("VDN: " D_CHECKSUM_FAILURE)); + return false; + } + + // sample data: + // 16 11 0b 00 00 00 0c 00 00 03 cb 00 00 00 0c 01 00 00 00 e7 + // |pm2_5| |pm1_0| |pm10 | | CRC | + Vindriktning.pm2_5 = (buffer[5] << 8) | buffer[6]; + Vindriktning.pm1_0 = (buffer[9] << 8) | buffer[10]; + Vindriktning.pm10 = (buffer[13] << 8) | buffer[14]; + + if (!Vindriktning.discovery_triggered) { + TasmotaGlobal.discovery_counter = 1; // force TasDiscovery() + Vindriktning.discovery_triggered = true; + } + return true; +} + +/*********************************************************************************************/ + +void VindriktningSecond(void) { // Every second + if (VindriktningReadData()) { + Vindriktning.valid = MIN_INTERVAL_PERIOD; + } else { + if (Vindriktning.valid) { + Vindriktning.valid--; + } + } +} + +/*********************************************************************************************/ + +void VindriktningInit(void) { + Vindriktning.type = 0; + if (PinUsed(GPIO_VINDRIKTNING_RX)) { + VindriktningSerial = new TasmotaSerial(Pin(GPIO_VINDRIKTNING_RX), -1, 1); + if (VindriktningSerial->begin(9600)) { + if (VindriktningSerial->hardwareSerial()) { ClaimSerial(); } + Vindriktning.type = 1; + } + } +} + +#ifdef USE_WEBSERVER +const char HTTP_VINDRIKTNING_SNS[] PROGMEM = + "{s}VINDRIKTNING " D_ENVIRONMENTAL_CONCENTRATION " 1.0 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}VINDRIKTNING " D_ENVIRONMENTAL_CONCENTRATION " 2.5 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}" + "{s}VINDRIKTNING " D_ENVIRONMENTAL_CONCENTRATION " 10 " D_UNIT_MICROMETER "{m}%d " D_UNIT_MICROGRAM_PER_CUBIC_METER "{e}"; // {s} = , {m} = , {e} = +#endif // USE_WEBSERVER + +void VindriktningShow(bool json) { + if (Vindriktning.valid) { + if (json) { + ResponseAppend_P(PSTR(",\"VINDRIKTNING\":{\"PM1\":%d,\"PM2.5\":%d,\"PM10\":%d}"), + Vindriktning.pm1_0, Vindriktning.pm2_5, Vindriktning.pm10); +#ifdef USE_DOMOTICZ + if (0 == TasmotaGlobal.tele_period) { + DomoticzSensor(DZ_COUNT, Vindriktning.pm1_0); // PM1.0 + DomoticzSensor(DZ_VOLTAGE, Vindriktning.pm2_5); // PM2.5 + DomoticzSensor(DZ_CURRENT, Vindriktning.pm10); // PM10 + } +#endif // USE_DOMOTICZ +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_VINDRIKTNING_SNS, Vindriktning.pm1_0, Vindriktning.pm2_5, Vindriktning.pm10); +#endif // USE_WEBSERVER + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns91(uint8_t function) { + bool result = false; + + if (Vindriktning.type) { + switch (function) { + case FUNC_EVERY_SECOND: + VindriktningSecond(); + break; + case FUNC_JSON_APPEND: + VindriktningShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + VindriktningShow(0); + break; +#endif // USE_WEBSERVER + case FUNC_INIT: + VindriktningInit(); + break; + } + } + return result; +} + +#endif // USE_VINDRIKTNING \ No newline at end of file diff --git a/tools/decode-status.py b/tools/decode-status.py index b2f54a711..ffbbca5b0 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -256,7 +256,7 @@ a_features = [[ "USE_MPU_ACCEL","USE_TFMINIPLUS","USE_CSE7761","USE_BERRY", "USE_BM8563","USE_ENERGY_DUMMY","USE_AM2320","USE_T67XX", "USE_MCP2515","USE_TASMESH","USE_WIFI_RANGE_EXTENDER","USE_INFLUXDB", - "","","","", + "USE_HRG15","USE_VINDRIKTNING","","", "","","","", "","","","", "","","","", @@ -288,7 +288,7 @@ else: obj = json.load(fp) def StartDecode(): - print ("\n*** decode-status.py v20210812 by Theo Arends and Jacek Ziolkowski ***") + print ("\n*** decode-status.py v20210826 by Theo Arends and Jacek Ziolkowski ***") # print("Decoding\n{}".format(obj))