Compare commits
661 Commits
feature/se
...
staging
Author | SHA1 | Date |
---|---|---|
Rick Carlino | c93be153e6 | |
Rick Carlino | c2b92722a8 | |
Rick Carlino | 8e08b9d182 | |
Rick Carlino | 1047f56e1c | |
Rick Carlino | e897b763f5 | |
Rick Carlino | 064ee310d9 | |
gabrielburnworth | 6cf1dc9159 | |
gabrielburnworth | c5929a7429 | |
Rick Carlino | 6deea2785d | |
Rick Carlino | 1158263405 | |
Rick Carlino | 6422ba1bcd | |
Rick Carlino | 65d50540d2 | |
Rick Carlino | 336b21ef31 | |
gabrielburnworth | d03e5d9ec7 | |
gabrielburnworth | 3901b02913 | |
Rick Carlino | 8309412f49 | |
Rick Carlino | 7431ecf800 | |
Rick Carlino | 2eb178279f | |
Rick Carlino | 27da00be16 | |
Rick Carlino | 7355fd25fb | |
Rick Carlino | 3b256f14f6 | |
Rick Carlino | c5b40d9b6d | |
Rick Carlino | 5abd596957 | |
Rick Carlino | 75604c5b34 | |
Rick Carlino | 4317a32a1c | |
Rick Carlino | e590b5eb6b | |
Rick Carlino | 12ee219ba6 | |
Rick Carlino | 004f7bb1d7 | |
Gabriel Burnworth | 6255515fa2 | |
Rick Carlino | 881585f254 | |
Rick Carlino | aecb77b48a | |
Rick Carlino | 02f16082b9 | |
Rick Carlino | 70b5fb18bb | |
Rick Carlino | e1f79aeaf5 | |
gabrielburnworth | 13fa880204 | |
Gabriel Burnworth | 3449864bc5 | |
Rick Carlino | 1807e5c0d3 | |
Rick Carlino | 88b440ee59 | |
Rick Carlino | ee517b2f9b | |
Rick Carlino | b9fca35731 | |
Rick Carlino | 6d92a11ebd | |
Rick Carlino | db1d6cf4f5 | |
Rick Carlino | 6a07b8539f | |
Rick Carlino | 10695dcc98 | |
Rick Carlino | 71e7c72329 | |
Rick Carlino | a64eb35ada | |
Rick Carlino | 786fd450be | |
Rick Carlino | 229aa645c9 | |
Rick Carlino | 6ffbbe93d5 | |
Rick Carlino | 717a0d7544 | |
Rick Carlino | 049411f272 | |
Rick Carlino | f0ea202196 | |
Rick Carlino | 285fb1a491 | |
Rick Carlino | 4694108cfd | |
Rick Carlino | 755cfb6f9e | |
Rick Carlino | 4a31b34bf4 | |
Rick Carlino | 53a87744f3 | |
Rick Carlino | b820a40ac4 | |
Rick Carlino | e1b3cec859 | |
Rick Carlino | d99d93c0b9 | |
Rick Carlino | 0ccb44ff41 | |
Rick Carlino | 420c87601e | |
Rick Carlino | 06d9dcb69d | |
Rick Carlino | 7a0978e2dd | |
Rick Carlino | de2233ccaf | |
Rick Carlino | 4be016281d | |
Rick Carlino | 0b9dc6eafe | |
Rick Carlino | 6cc44a07b0 | |
Rick Carlino | 007650df4a | |
Rick Carlino | 323f853891 | |
Rick Carlino | 8d7fb9a010 | |
Rick Carlino | b1c77657cd | |
Rick Carlino | bc3143465d | |
Rick Carlino | e023abd007 | |
Rick Carlino | 0dd25156c7 | |
Rick Carlino | 03555bf7eb | |
Rick Carlino | 07a77c0d4b | |
Rick Carlino | 18b5f3779b | |
Rick Carlino | 471b9f2543 | |
Rick Carlino | 4a30a75cac | |
Rick Carlino | 38c0fcb5dc | |
Rick Carlino | 2469cb0168 | |
Rick Carlino | 92dea8b0a1 | |
gabrielburnworth | 13279262af | |
Rick Carlino | 20cf44ddad | |
Rick Carlino | 3b66b164a7 | |
Rick Carlino | e2696cd93b | |
Rick Carlino | 8b48613783 | |
Rick Carlino | 539f19c360 | |
Rick Carlino | 75bbeb7281 | |
Rick Carlino | 816da6128a | |
Rick Carlino | c3f446d31f | |
Rick Carlino | c0670ec19a | |
Rick Carlino | 4fab4bdbb6 | |
Rick Carlino | ae8517b45d | |
Rick Carlino | dd3b5f2d40 | |
Rick Carlino | 47d66b4701 | |
Rick Carlino | 245bf3e0e6 | |
Rick Carlino | 968179322a | |
Rick Carlino | bce9f4385f | |
Rick Carlino | 856e19147e | |
Rick Carlino | c7653e14a0 | |
Rick Carlino | 0df4e78ba5 | |
Gabriel Burnworth | 1bb175a387 | |
Rick Carlino | fefd444aa0 | |
Rick Carlino | 8d70e8c454 | |
Rick Carlino | 643bbe8475 | |
Rick Carlino | dac8fc7240 | |
Rick Carlino | ccd34f9943 | |
Rick Carlino | a57f44976b | |
Rick Carlino | 74291a7cc5 | |
Rick Carlino | 37222d09d6 | |
Rick Carlino | a55295f596 | |
Rick Carlino | 8c4ee03193 | |
Rick Carlino | 5eeebba6cf | |
Rick Carlino | 7ca2725540 | |
Rick Carlino | bfb4f21f54 | |
Rick Carlino | 7407119942 | |
Rick Carlino | b149504ca2 | |
Rick Carlino | 92121b4955 | |
Rick Carlino | 88da7a298c | |
Rick Carlino | 626fb4e5a4 | |
Rick Carlino | f463c698e0 | |
Rick Carlino | 446aeb007d | |
Rick Carlino | 87fcc9f5a9 | |
Rick Carlino | c9dc517df6 | |
Rick Carlino | c82cd1f165 | |
Rick Carlino | c9174cf1d2 | |
Rick Carlino | 96867553e4 | |
Rick Carlino | 5eeec86db4 | |
Rick Carlino | 609150d96e | |
Rick Carlino | 24d02d6a6c | |
Rick Carlino | e5a0c40389 | |
Rick Carlino | a69decb139 | |
Rick Carlino | 0fc39b1500 | |
Rick Carlino | ebb9ff41e7 | |
Rick Carlino | c5e69e0c3d | |
Rick Carlino | fe2666e768 | |
Rick Carlino | 048e6db5fa | |
Rick Carlino | 73a304da56 | |
Rick Carlino | 992d9d4264 | |
Rick Carlino | b3a2b49f49 | |
Rick Carlino | abd6749434 | |
Rick Carlino | 726da83ed1 | |
Rick Carlino | 1749830a31 | |
Rick Carlino | b6e2b83903 | |
Rick Carlino | 6e674d0e31 | |
Rick Carlino | ebc371c4d0 | |
Rick Carlino | 298334aac5 | |
Rick Carlino | 5b5101313b | |
Rick Carlino | 939afb52a4 | |
Rick Carlino | 0a7803e202 | |
Rick Carlino | d091f52371 | |
Rick Carlino | 7d437d64f4 | |
Rick Carlino | 427f317de0 | |
Rick Carlino | d224d6ce7a | |
Rick Carlino | 1469ab47ac | |
Rick Carlino | b477b74c37 | |
Rick Carlino | 4c6d21d0a7 | |
Rick Carlino | 5dfb9d3177 | |
Rick Carlino | d513794868 | |
Rick Carlino | 7ea9e22c7d | |
Rick Carlino | d4e6ec2e20 | |
Rick Carlino | 7293ea142b | |
Rick Carlino | c2141cc835 | |
Rick Carlino | a6b7305c3b | |
Rick Carlino | 4940fd88c7 | |
Rick Carlino | a57ec7c08d | |
Rick Carlino | 11b00a7fa4 | |
Rick Carlino | f8ff6db88d | |
Rick Carlino | 878627f7c8 | |
Rick Carlino | 4138071c1a | |
Rick Carlino | 7e4f133d96 | |
Rick Carlino | c34aa56e17 | |
Rick Carlino | 52f0e6fba0 | |
Rick Carlino | 19d42c5a25 | |
Rick Carlino | 161e97cf0a | |
Rick Carlino | e8caa769e3 | |
Rick Carlino | b93bd9c142 | |
Rick Carlino | 4299721463 | |
Rick Carlino | faf88bb925 | |
Rick Carlino | bb7831f1a1 | |
Rick Carlino | 0f24ed894c | |
Rick Carlino | fea135163e | |
Rick Carlino | 99ba1019a2 | |
Rick Carlino | 63d03599dd | |
Rick Carlino | cadaea1e3a | |
Rick Carlino | e378c0c665 | |
Rick Carlino | b3e4b58696 | |
Rick Carlino | 10f90a9a91 | |
Rick Carlino | 680f208e97 | |
Rick Carlino | bb4f910cfc | |
Rick Carlino | 701141b0a3 | |
Rick Carlino | 7108599645 | |
Rick Carlino | 09ae87e5b0 | |
Rick Carlino | 1f7f250f48 | |
Rick Carlino | 477f9a8dd8 | |
Rick Carlino | 681421e235 | |
Rick Carlino | 10b7c380b7 | |
Rick Carlino | bcb0dae229 | |
Rick Carlino | 0c4b14eb1b | |
Rick Carlino | 53f28daefe | |
Rick Carlino | 1aac649e9b | |
Rick Carlino | 78e44eeb81 | |
Rick Carlino | 431e05284a | |
Rick Carlino | 04c74b28f2 | |
Rick Carlino | e5a29361d2 | |
Rick Carlino | aa6f3f4ad1 | |
Rick Carlino | 06323242c2 | |
Rick Carlino | ec62607648 | |
Rick Carlino | 7fad9e7231 | |
Rick Carlino | fe77262626 | |
Rick Carlino | 33b0947c7a | |
Rick Carlino | 9a5b927953 | |
Rick Carlino | 128ed8ec43 | |
Rick Carlino | e6b0f53224 | |
Rick Carlino | 63056be70b | |
Rick Carlino | 442e6b1266 | |
Rick Carlino | 7e27e0e47a | |
Rick Carlino | 3ff10e0097 | |
Rick Carlino | 016b9cf9dd | |
Rick Carlino | 742f2522cf | |
Rick Carlino | 54c9438f3f | |
Rick Carlino | a9a519aee0 | |
Rick Carlino | b6fc53634d | |
Rick Carlino | 70d624c4c5 | |
Rick Carlino | a4dfb84b95 | |
Rick Carlino | 473c4904fc | |
Rick Carlino | 1ab623a2b8 | |
Rick Carlino | cd1061e681 | |
Rick Carlino | a5226b0bef | |
Rick Carlino | 737559b215 | |
Rick Carlino | 93d3b57178 | |
Rick Carlino | a1708dea8a | |
Rick Carlino | aececd69f5 | |
Rick Carlino | 46064b802c | |
Rick Carlino | b396b47dc2 | |
Rick Carlino | d5e10d7701 | |
Rick Carlino | 1a38833722 | |
Rick Carlino | 7f190d2e44 | |
Rick Carlino | 6e3333c435 | |
Rick Carlino | c44975414e | |
Rick Carlino | 970c47a25d | |
Rick Carlino | 58eef13d16 | |
Rick Carlino | 5a85f840ac | |
Rick Carlino | f92b01f7f6 | |
Rick Carlino | 2b7ea92275 | |
Rick Carlino | a4b33c3a59 | |
gabrielburnworth | c04f6e46f8 | |
Rick Carlino | 89842cd641 | |
Rick Carlino | 2cc245192d | |
Rick Carlino | 6f7bd74abe | |
Rick Carlino | bcbcb85f66 | |
Rick Carlino | ba7e582e4a | |
Rick Carlino | e940b2c940 | |
Rick Carlino | b26bf4f59f | |
Rick Carlino | 8f39bf2b71 | |
Rick Carlino | ab09e31a0e | |
Rick Carlino | f23a632682 | |
Rick Carlino | 59fb3fe50a | |
Rick Carlino | fe0ad9d914 | |
Rick Carlino | c903a0efc4 | |
Rick Carlino | 2f4b0a5754 | |
Rick Carlino | 6440aacdb8 | |
Rick Carlino | f8b36dd6a9 | |
Rick Carlino | e25a30a344 | |
Rick Carlino | 9cd28db4b1 | |
Rick Carlino | 3b76ea17e8 | |
Rick Carlino | 06926b05ce | |
Rick Carlino | 9ff51f40d8 | |
Rick Carlino | 14f988ec30 | |
Rick Carlino | 94314f73a1 | |
Rick Carlino | 94e44a348d | |
Rick Carlino | 655efccbc2 | |
Rick Carlino | d409293211 | |
Rick Carlino | 5f1e7e9171 | |
Rick Carlino | efa2bff829 | |
gabrielburnworth | bd06773726 | |
Rick Carlino | 1d85b95a48 | |
Rick Carlino | 5ef5bb7b91 | |
Rick Carlino | fb118002e4 | |
Rick Carlino | 1f26485f88 | |
Rick Carlino | 0058d1b01e | |
Rick Carlino | 9216cf1456 | |
Rick Carlino | 9bdae98cad | |
Rick Carlino | 835e535e40 | |
Rick Carlino | 19bc0b120d | |
Rick Carlino | a7b476a601 | |
Rick Carlino | 7de9dfe033 | |
Rick Carlino | 4bd6a44afc | |
Rick Carlino | 6b4290f77c | |
Rick Carlino | ca54de1a7a | |
Rick Carlino | ee57987271 | |
Rick Carlino | e769a879b8 | |
Rick Carlino | c1292c5c08 | |
Rick Carlino | 519e5a3804 | |
Rick Carlino | 2b7bfb410d | |
Rick Carlino | d518e8c891 | |
Rick Carlino | bee7a0aaf9 | |
Rick Carlino | b85ab26b5e | |
Rick Carlino | 037c2b3a27 | |
Rick Carlino | ac0f929e2e | |
gabrielburnworth | 28c2d66323 | |
Rick Carlino | f11e8fbc4b | |
Rick Carlino | 5d0b11bb81 | |
Rick Carlino | afec9d13bd | |
Rick Carlino | e06a53040f | |
Rick Carlino | 61d5673e37 | |
Rick Carlino | 5bab149008 | |
Rick Carlino | 22818d6c8f | |
Rick Carlino | 0fab548533 | |
Rick Carlino | 2a62a0bbeb | |
Rick Carlino | d991c6e558 | |
Rick Carlino | 4c9e054190 | |
Rick Carlino | 8e350f0a1b | |
Rick Carlino | 585fda2cc3 | |
Rick Carlino | f7a345e9bf | |
Rick Carlino | 6a961390e6 | |
Rick Carlino | 471269b7d5 | |
Rick Carlino | d4d7cc020b | |
Rick Carlino | bf4c62ecbc | |
Rick Carlino | a8fa3044b5 | |
Rick Carlino | fe4d64753f | |
Rick Carlino | 0ad05f07d4 | |
Rick Carlino | 3be4d58308 | |
Rick Carlino | 5fef363475 | |
Rick Carlino | d0f09a1ee9 | |
Rick Carlino | 987eaab83f | |
Rick Carlino | 5f4e26cc58 | |
Rick Carlino | 6299407bc2 | |
Rick Carlino | f43ab2c23d | |
Rick Carlino | 9ebd6e2a73 | |
Rick Carlino | 9e04ad0329 | |
Rick Carlino | 41b38f7666 | |
Rick Carlino | 25f8cc61a5 | |
gabrielburnworth | 36b84ee13d | |
Rick Carlino | 121a6a52a2 | |
Rick Carlino | f79842bbc9 | |
Rick Carlino | ed20ceefc5 | |
Rick Carlino | 9d932c8025 | |
Rick Carlino | 205fa6afd6 | |
Rick Carlino | 91a57ef5af | |
Rick Carlino | 2dc602c58c | |
Rick Carlino | f066ff691a | |
Rick Carlino | 22a70d0c2c | |
gabrielburnworth | 42ff39376c | |
gabrielburnworth | 694929f551 | |
gabrielburnworth | 122efed214 | |
Rick Carlino | 2c724e2a85 | |
Rick Carlino | 153ab20362 | |
Rick Carlino | 8de41baa83 | |
Rick Carlino | 7e3c252eeb | |
Rick Carlino | ef78de66eb | |
Rick Carlino | 291a254059 | |
Rick Carlino | aac4244b90 | |
Rick Carlino | 8eecadd06e | |
Rick Carlino | e8814c2985 | |
Rick Carlino | 835c033850 | |
Rick Carlino | 4878c8fc36 | |
Rick Carlino | dfe6ff0fe4 | |
Rick Carlino | 26fdbe1502 | |
Rick Carlino | 61d51a3a0c | |
Rick Carlino | a56d381959 | |
Rick Carlino | a939c4f812 | |
Rick Carlino | 70a813317a | |
Rick Carlino | 0b4d4e830c | |
Rick Carlino | 27a24d6413 | |
Rick Carlino | 2cd5c936d4 | |
Rick Carlino | 7555ccd05e | |
Rick Carlino | 0fed24eadb | |
Rick Carlino | f5567494bb | |
Rick Carlino | e0068bd3c2 | |
Rick Carlino | 314f510b0e | |
Rick Carlino | 585c1ff93f | |
Rick Carlino | 7d2d4aa216 | |
Rick Carlino | 23f668a321 | |
Rick Carlino | d6b7ee774d | |
Rick Carlino | 24e573f72c | |
Rick Carlino | cf370f2e06 | |
Rick Carlino | de0953b5bf | |
gabrielburnworth | 0e28da3f3a | |
Rick Carlino | 6ad22199fb | |
Rick Carlino | 1cde334792 | |
Rick Carlino | 4918a75b62 | |
Rick Carlino | 1c7e330cb5 | |
Rick Carlino | 02c87a010e | |
Rick Carlino | 4ae256d03b | |
Rick Carlino | 510575b596 | |
Rick Carlino | 7ec1b3069c | |
Rick Carlino | b59ece24fb | |
Rick Carlino | c8536db446 | |
Rick Carlino | 1dcff8e45a | |
Rick Carlino | a9e03bcfce | |
Rick Carlino | 33b5c55146 | |
Rick Carlino | cfc420cf8e | |
Rick Carlino | 5d4a79b137 | |
Rick Carlino | 5a8df7234c | |
Rick Carlino | 78ea7d17c3 | |
Rick Carlino | a842948d03 | |
Rick Carlino | 6269a57245 | |
Rick Carlino | b4fd67aaad | |
Rick Carlino | 53233d20b0 | |
Rick Carlino | da51b54bd9 | |
Rick Carlino | 03da1dc96b | |
Rick Carlino | fbf9445f1e | |
gabrielburnworth | 9ba78c07df | |
Rick Carlino | 796ef547ef | |
Rick Carlino | a5c2c3c740 | |
Rick Carlino | e98b17f2df | |
Rick Carlino | 11b38b47db | |
Rick Carlino | 6ed34124e8 | |
Rick Carlino | 6e033b0de8 | |
Rick Carlino | 938f7fd04e | |
Rick Carlino | a916fbd51d | |
Gabriel Burnworth | ef43e44625 | |
gabrielburnworth | b91f3680ce | |
Rick Carlino | cb384e3323 | |
Rick Carlino | 49981ac9fb | |
Rick Carlino | f457e320f8 | |
Rick Carlino | d7c147257a | |
Rick Carlino | 830dc25dbc | |
Rick Carlino | 092d611ca8 | |
Rick Carlino | 4cff9f3b97 | |
Rick Carlino | e7aa6724ca | |
Rick Carlino | db0e179512 | |
Rick Carlino | b87a582579 | |
Rick Carlino | 16c2a5cd98 | |
Rick Carlino | b0da67b847 | |
Rick Carlino | 594b24553a | |
Rick Carlino | cb37576908 | |
Rick Carlino | 6e116243e9 | |
Rick Carlino | 61a92ec25a | |
Rick Carlino | 7dd311a840 | |
Rick Carlino | e696f7c895 | |
Rick Carlino | 5f93d6a03b | |
Rick Carlino | fabd36fc80 | |
Rick Carlino | e240e6b2f1 | |
John Simmonds | 6df0793e69 | |
Rick Carlino | b5a5a6a41d | |
Rick Carlino | 38ee343194 | |
Rick Carlino | ff760cce70 | |
Rick Carlino | 7a744677dc | |
Rick Carlino | ea871f1563 | |
Rick Carlino | d212710254 | |
Rick Carlino | 03d61d70c6 | |
Rick Carlino | ab5f3b33db | |
Rick Carlino | 0747191a22 | |
Rick Carlino | 3019782de6 | |
Rick Carlino | 1bc2cbfe10 | |
John Simmonds | 935a6acfc3 | |
gabrielburnworth | 01e97de2a0 | |
Rick Carlino | 132d737bc9 | |
Rick Carlino | 03874c0bfa | |
Rick Carlino | ba8c0caa3d | |
Rick Carlino | 2ab6911323 | |
Rick Carlino | b7c9e3524b | |
Rick Carlino | 716004ee74 | |
Rick Carlino | 340bbee970 | |
Rick Carlino | cf8fccce52 | |
Rick Carlino | b772e9d4fc | |
Rick Carlino | 8e2626ce71 | |
Rick Carlino | 584071408a | |
Rick Carlino | 5f5517a276 | |
Rick Carlino | 8429aa2331 | |
Rick Carlino | 1dee7fbfbe | |
Rick Carlino | 0478df98ea | |
Rick Carlino | 6fa5c5ad42 | |
Rick Carlino | c2cbadb636 | |
Rick Carlino | f282f630b2 | |
Rick Carlino | 8ae9335c53 | |
Rick Carlino | 1a0d5732e9 | |
Rick Carlino | b2fea77665 | |
Rick Carlino | 8d657d832f | |
Rick Carlino | a204c790ac | |
gabrielburnworth | c56b4e0d6d | |
Rick Carlino | 4383f54c9a | |
Rick Carlino | edd52563db | |
Rick Carlino | c4f1cc3aa5 | |
Rick Carlino | 61efe7d97b | |
gabrielburnworth | f226fb3329 | |
Rick Carlino | 6686f7d60a | |
Rick Carlino | a94af9d102 | |
Gabriel Burnworth | ed32625f3d | |
Rick Carlino | 5502218bd7 | |
Rick Carlino | a4018a7763 | |
Rick Carlino | 5ef053aeec | |
Rick Carlino | 56ccf70ad8 | |
Rick Carlino | d598ab6280 | |
Rick Carlino | 1c568f680a | |
Rick Carlino | 0b2cc362da | |
Rick Carlino | 783025eeca | |
Rick Carlino | 4faab5fe6a | |
Rick Carlino | 1fce146504 | |
Rick Carlino | c8e289409c | |
Rick Carlino | a248d98f83 | |
Rick Carlino | 5da2c58a44 | |
Rick Carlino | dbf6ba5739 | |
Rick Carlino | 2938d74927 | |
Rick Carlino | 3e34d158fd | |
Rick Carlino | f0b0ff10b0 | |
Rick Carlino | f7410c3a5c | |
Rick Carlino | e99e590cb5 | |
Rick Carlino | 024b239b80 | |
Rick Carlino | de7a874845 | |
Rick Carlino | 74788346c7 | |
Rick Carlino | 06a18f3717 | |
Rick Carlino | c5357b990f | |
Rick Carlino | 1fd1b2bbcb | |
Rick Carlino | 020383a88b | |
Rick Carlino | 1a07ad087c | |
Rick Carlino | e518122105 | |
Rick Carlino | 25f69665fb | |
Rick Carlino | 52056bc77d | |
Rick Carlino | a1b6b41a51 | |
Rick Carlino | 5f86f1fe3c | |
Rick Carlino | 3007ec4509 | |
Rick Carlino | 28c631eb8f | |
Rick Carlino | 1bdb46bb68 | |
Rick Carlino | 795f7a326d | |
Rick Carlino | 91b664f7df | |
Rick Carlino | dd8bfb5f3c | |
Rick Carlino | 97ce3d03a0 | |
Rick Carlino | dba657edc6 | |
Rick Carlino | f67a4d2f4f | |
Rick Carlino | 986ac6884d | |
Rick Carlino | b36044662a | |
Rick Carlino | b5cad63a55 | |
Rick Carlino | cd0a0a3a13 | |
Rick Carlino | b59075c9aa | |
Rick Carlino | 80d353664b | |
Rick Carlino | a48e821f1f | |
Rick Carlino | b8344763e8 | |
Rick Carlino | 6c270960b0 | |
Rick Carlino | 16b7ab8af4 | |
Rick Carlino | 3a788d86f7 | |
Rick Carlino | a4c342f4cf | |
Rick Carlino | fb93dac7cd | |
Rick Carlino | 6fbc957b47 | |
Rick Carlino | 9f95971b45 | |
gabrielburnworth | fef3a3bf5c | |
gabrielburnworth | 404e49c31a | |
Rick Carlino | 990c7a6c13 | |
Rick Carlino | 0af92ff967 | |
Rick Carlino | 064e6d309b | |
Rick Carlino | 54cb1dac13 | |
Rick Carlino | 1b8f10590f | |
Rick Carlino | 2a0e5aef6c | |
Rick Carlino | deb72cd177 | |
Rick Carlino | fc2831d58e | |
Rick Carlino | a52ccb42b6 | |
Rick Carlino | aae4751568 | |
Rick Carlino | 5bd80e29ca | |
Rick Carlino | 12497e008d | |
Rick Carlino | 3d61668446 | |
gabrielburnworth | 7160c1f13a | |
Rick Carlino | a44448f86b | |
Rick Carlino | c6f5c6270a | |
Rick Carlino | e3d33a926a | |
Rick Carlino | 8f69f00ffc | |
Rick Carlino | 0b8d211e6b | |
Rick Carlino | f6c37e423b | |
Rick Carlino | abb0e6696c | |
Rick Carlino | 8a7e8e1096 | |
Rick Carlino | a2694185a7 | |
Rick Carlino | 1620c422cc | |
Rick Carlino | 2e8f66eeb4 | |
Rick Carlino | 7c5bbb7da7 | |
Rick Carlino | aed3b51be0 | |
Rick Carlino | a0b5bff980 | |
Rick Carlino | b83be2fda2 | |
Rick Carlino | 08aeed1cf5 | |
Rick Carlino | c830099dd2 | |
Rick Carlino | ee4606b03b | |
Rick Carlino | 7059f3c44c | |
Rick Carlino | 407f7f5fee | |
gabrielburnworth | e0765016d8 | |
Rick Carlino | 482c590b40 | |
Rick Carlino | 7812b761e9 | |
Rick Carlino | e98ef0e88f | |
Rick Carlino | 844d521537 | |
Rick Carlino | e83ada7e93 | |
Rick Carlino | ff1576c171 | |
Rick Carlino | 1e37e5cf5a | |
Rick Carlino | ce7af2155c | |
Rick Carlino | fc353c64d5 | |
Rick Carlino | 3316baad82 | |
Rick Carlino | ce4d3f1688 | |
Rick Carlino | d6ab57425b | |
Rick Carlino | 9ba060c0f1 | |
Rick Carlino | e1e8adfa00 | |
Rick Carlino | f2790ff886 | |
Rick Carlino | e4af444ece | |
Rick Carlino | 0c22021d66 | |
Rick Carlino | ac7bf38be9 | |
Rick Carlino | c9206ab484 | |
Rick Carlino | 2c45bd3057 | |
Rick Carlino | 2f7685c077 | |
Rick Carlino | f3f8316cc3 | |
Rick Carlino | 21a1201a06 | |
Rick Carlino | 7b5b564204 | |
Rick Carlino | feae00c8db | |
Rick Carlino | 7e5738f883 | |
Rick Carlino | 111541c4b3 | |
Rick Carlino | 82028f7443 | |
Rick Carlino | 487fc9ca2e | |
Rick Carlino | 99b5f3436b | |
Rick Carlino | a8741d6d6b | |
Rory Aronson | 413d35e299 | |
Rick Carlino | 92fd788098 | |
Rick Carlino | 596c3cc659 | |
Rick Carlino | cb6c83e368 | |
Rick Carlino | 66bc2cc3b0 | |
Rick Carlino | 834efe05b3 | |
Rick Carlino | 8e495409e3 | |
Rick Carlino | ebc5d89bda | |
Rick Carlino | fc24a780da | |
Rick Carlino | 72a5dfe688 | |
Rick Carlino | 5ef4f02193 | |
Rick Carlino | dca6fbbd33 | |
Rick Carlino | 5a449af74f | |
Rick Carlino | a2647f7fe2 | |
Rick Carlino | cc1a97d886 | |
Rick Carlino | eba61d34f8 | |
Rick Carlino | 88ace0db19 | |
Rick Carlino | 4ce80f0431 | |
Rick Carlino | ee010874f0 | |
Rick Carlino | a8c4887841 | |
Rick Carlino | 26d31012fb | |
Rick Carlino | 12c55fb60f | |
Rick Carlino | 1c00c543e3 | |
Rick Carlino | 779c450545 | |
Rick Carlino | 7a6c544e21 | |
Rick Carlino | 5722bea421 | |
Rick Carlino | a13e6c5832 | |
Rick Carlino | 2d17b4e82f | |
Rick Carlino | a61c69c816 | |
Rick Carlino | 010cf90348 | |
Rick Carlino | 82f63dd0cc | |
Rick Carlino | 4b92386534 | |
Rick Carlino | 0f24d4474b | |
Rick Carlino | 87398438b4 | |
Rick Carlino | 8d731d1590 | |
Gabriel Burnworth | c5c8bb4428 | |
Rick Carlino | 0cb0361e29 | |
Rick Carlino | 5a95d6c30d | |
Rick Carlino | 82fc90c0ec | |
Rick Carlino | 10ea92805f | |
Rick Carlino | 6ad51907e6 | |
Rick Carlino | 958d5c8f09 | |
Rick Carlino | bd86cfe7a7 | |
Rick Carlino | fd9886f23c | |
Rick Carlino | 5c04f54cd8 | |
Rick Carlino | 0d54937946 | |
Rick Carlino | d9ba9f25ca | |
Rick Carlino | 2f640c5d13 | |
Rick Carlino | e2548b3af4 | |
Rick Carlino | b33be76849 | |
Rick Carlino | f7582e8922 | |
Rick Carlino | 57cc8a8b37 | |
Rick Carlino | 013d617538 | |
Connor Rigby | 6fbb0c6ac8 |
|
@ -2,7 +2,7 @@ version: 2.0
|
|||
defaults: &defaults
|
||||
working_directory: /nerves/build
|
||||
docker:
|
||||
- image: nervesproject/nerves_system_br:latest
|
||||
- image: nervesproject/nerves_system_br:1.11.3
|
||||
|
||||
install_elixir: &install_elixir
|
||||
run:
|
||||
|
@ -162,7 +162,9 @@ deploy_nerves_hub_firmware_steps: &deploy_nerves_hub_firmware_steps
|
|||
- run:
|
||||
name: Sign Image
|
||||
working_directory: /nerves/build/farmbot_os
|
||||
command: mix nerves_hub.firmware sign --key notreal /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat ../VERSION).fw
|
||||
command: |
|
||||
mix deps.get
|
||||
mix nerves_hub.firmware sign --key notreal /nerves/deploy/system/artifacts/farmbot-${MIX_TARGET}-$(cat ../VERSION).fw
|
||||
- run:
|
||||
name: Publish to NervesHub
|
||||
working_directory: /nerves/build/farmbot_os
|
||||
|
@ -199,12 +201,13 @@ jobs:
|
|||
mix compile
|
||||
mix format --check-formatted
|
||||
mix coveralls.json
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: v14-fbcs-test-dependency-cache-{{ checksum "farmbot_celery_script/mix.lock" }}
|
||||
paths:
|
||||
- farmbot_celery_script/_build/test
|
||||
- farmbot_celery_script/deps
|
||||
- save_cache:
|
||||
- save_cache:
|
||||
key: v14-fbcs-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- farmbot_celery_script/cover
|
||||
|
@ -233,12 +236,13 @@ jobs:
|
|||
mix compile
|
||||
mix format --check-formatted
|
||||
mix coveralls.json
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: v14-fbfw-test-dependency-cache-{{ checksum "farmbot_firmware/mix.lock" }}
|
||||
paths:
|
||||
- farmbot_firmware/_build/test
|
||||
- farmbot_firmware/deps
|
||||
- save_cache:
|
||||
- save_cache:
|
||||
key: v14-fbfw-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- farmbot_firmware/cover
|
||||
|
@ -277,13 +281,14 @@ jobs:
|
|||
mix compile
|
||||
mix format --check-formatted
|
||||
mix coveralls.json --trace
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: v14-fbcore-test-dependency-cache-{{ checksum "farmbot_core/mix.lock" }}
|
||||
paths:
|
||||
- farmbot_core/_build/test
|
||||
- farmbot_core/deps
|
||||
- farmbot_core/arduino
|
||||
- save_cache:
|
||||
- save_cache:
|
||||
key: v14-fbcore-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- farmbot_core/cover
|
||||
|
@ -325,12 +330,13 @@ jobs:
|
|||
mix ecto.create
|
||||
mix ecto.migrate
|
||||
mix coveralls.json
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: v14-fbext-test-dependency-cache-{{ checksum "farmbot_ext/mix.lock" }}
|
||||
paths:
|
||||
- farmbot_ext/_build/test
|
||||
- farmbot_ext/deps
|
||||
- save_cache:
|
||||
- save_cache:
|
||||
key: v14-fbext-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- farmbot_ext/cover
|
||||
|
@ -348,7 +354,7 @@ jobs:
|
|||
- checkout
|
||||
- run: git submodule update --init --recursive
|
||||
- restore_cache:
|
||||
keys:
|
||||
keys:
|
||||
- v14-fbos-host-test-dependency-cache-{{ checksum "farmbot_os/mix.lock" }}
|
||||
- <<: *install_elixir
|
||||
- <<: *install_hex_archives
|
||||
|
@ -360,69 +366,18 @@ jobs:
|
|||
mix compile
|
||||
mix format --check-formatted
|
||||
mix coveralls.json
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- save_cache:
|
||||
key: v14-fbos-host-test-dependency-cache-{{ checksum "farmbot_os/mix.lock" }}
|
||||
paths:
|
||||
- farmbot_os/_build/host
|
||||
- farmbot_os/deps/host
|
||||
- save_cache:
|
||||
- save_cache:
|
||||
key: v14-fbos-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- farmbot_os/cover
|
||||
- store_artifacts:
|
||||
path: farmbot_os/cover
|
||||
|
||||
report_coverage:
|
||||
<<: *defaults
|
||||
environment:
|
||||
MIX_ENV: test
|
||||
MIX_TARGET: host
|
||||
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
|
||||
ELIXIR_VERSION: 1.8.0
|
||||
steps:
|
||||
- checkout
|
||||
- run: git submodule update --init --recursive
|
||||
- <<: *install_elixir
|
||||
- <<: *install_hex_archives
|
||||
- <<: *install_mdl
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbsupport-test-dependency-cache-{{ checksum "mix.lock" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbcs-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbcs-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbfw-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbcore-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbext-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v14-fbos-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
||||
- run:
|
||||
name: Check documentation formatting
|
||||
command: |
|
||||
mdl docs/
|
||||
- run:
|
||||
name: Report Coverage
|
||||
working_directory: /nerves/build/
|
||||
command: |
|
||||
mix deps.get
|
||||
mix compile
|
||||
mix format --check-formatted
|
||||
mix farmbot.coveralls circle
|
||||
- save_cache:
|
||||
key: v14-fbsupport-test-dependency-cache-{{ checksum "mix.lock" }}
|
||||
paths:
|
||||
- deps/
|
||||
- _build
|
||||
|
||||
################################################################################
|
||||
# target=rpi app_env=prod #
|
||||
|
@ -717,22 +672,6 @@ workflows:
|
|||
- beta
|
||||
- next
|
||||
- /^qa\/.*/
|
||||
- report_coverage:
|
||||
context: org-global
|
||||
requires:
|
||||
- test_farmbot_celery_script
|
||||
- test_farmbot_firmware
|
||||
- test_farmbot_core
|
||||
- test_farmbot_ext
|
||||
- test_farmbot_os
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- staging
|
||||
- beta
|
||||
- next
|
||||
- /^qa\/.*/
|
||||
|
||||
# master branch to staging.farmbot.io
|
||||
nerves_hub_prod_stable_staging:
|
||||
|
|
70
CHANGELOG.md
70
CHANGELOG.md
|
@ -1,7 +1,77 @@
|
|||
# Changelog
|
||||
|
||||
# 10.0.0
|
||||
|
||||
* Deprecate `resource_update` RPC
|
||||
* Introduce `update_resource` RPC, which allows users to modify variables from the sequence editor.
|
||||
* Genesis v1.5 and Express v1.0 firmware updates.
|
||||
* Fix a bug where FBOS would not honor an "AUTO UPDATE" value of "false".
|
||||
|
||||
# 9.2.2
|
||||
|
||||
* Fix firmware locking error ("Can't perform X in Y state")
|
||||
* Removal of dead code / legacy plus numerous unit test additions.
|
||||
* Added coveralls test coverage reporter
|
||||
* Unit test additions (+2.7% coverage :tada:)
|
||||
* Updates to build instructions for third party developers
|
||||
* Bug fix for criteria-based groups that have only one filter criteria.
|
||||
* Bug fix for express bots involving timeout during remote firmware flash
|
||||
* Remove VCR again (for now)
|
||||
* Increase farmware timeout to 20 minutes (use at own risk)
|
||||
|
||||
# 9.2.1
|
||||
|
||||
* Improve firmware debug messages.
|
||||
* Remove confusing firmware debug messages, such as "Error OK".
|
||||
* Improved camera support on FarmBot express.
|
||||
* Bug fix to prevents OTA updates occuring when one is already in progress.
|
||||
|
||||
# 9.2.0
|
||||
|
||||
* Support for criteria-based groups.
|
||||
* Genesis v1.5 and Express v1.0 firmware homing updates.
|
||||
* Fix bug where unknown positions would report as -1.
|
||||
|
||||
# 9.1.2
|
||||
|
||||
* Genesis v1.5 and Express v1.0 firmware updates.
|
||||
* Bug fix for movement error reporting
|
||||
* Improved firmware error message reporting
|
||||
* Improved support for gantry mounted tools.
|
||||
|
||||
# 9.1.1
|
||||
|
||||
* Genesis v1.5 and Express v1.0 firmware updates.
|
||||
|
||||
# 9.1.0
|
||||
|
||||
* Improved support for new FarmBot Express models
|
||||
* Various firmware bug fixes for Express models.
|
||||
* Bug fix for slow Farmware execution (Thanks, @jsimmonds2)
|
||||
* Dependency upgrades
|
||||
* Upgrade VintageNet (networking library)
|
||||
* Removal of `dump_info` RPCs
|
||||
* Numerous internal improvements, such as increasing test coverage and changing dependency injection scheme.
|
||||
* Fix issue where firmware commands would be tried too many times.
|
||||
|
||||
# 9.0.4
|
||||
|
||||
* Bug fix for slow Farmware execution (Thanks, @jsimmonds2)
|
||||
* Dependency upgrades
|
||||
* Upgrade VintageNet (networking library)
|
||||
* Removal of `dump_info` RPCs
|
||||
* Numerous internal improvements, such as increasing test coverage and changing dependency injection scheme.
|
||||
|
||||
# 9.0.3
|
||||
|
||||
* Dependency updates
|
||||
|
||||
# 9.0.2
|
||||
* See notes for 9.0.1.
|
||||
|
||||
# 9.0.1
|
||||
* Routine token updates on Circle CI.
|
||||
* Fix bugs that were causing devices to erroneously factory reset under some circumstances.
|
||||
|
||||
# 9.0.0
|
||||
* Run updates on Nerves systems.
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# Jan - Mar 2020
|
||||
|
||||
| Project | Jan 1 20 | Feb 6 20 | Mar 4 20 |STATUS|
|
||||
|-----------------------|----------|----------|----------|------|
|
||||
| farmbot_celery_script | 53.7% | 54.0% | 54.0% |OK |
|
||||
| farmbot_core | 22.2% | 19.8% | 26.3% |OK |
|
||||
| farmbot_ext | 53.6% | 52.7% | 38.1% |FIX | !!!
|
||||
| farmbot_firmware | 13.8% | 56.4% | 62.0% |OK |
|
||||
| farmbot_os | 22.0% | 27.6% | 45.3% |OK |
|
||||
| farmbot_telemetry | ??.?% | ??.?% | ??.?% |LATER |
|
|
@ -5,6 +5,7 @@
|
|||
"assertion_block": "8.0.0",
|
||||
"backscheduled_regimens": "6.4.0",
|
||||
"change_ownership": "6.3.0",
|
||||
"criteria_groups": "9.2.2",
|
||||
"diagnostic_dumps": "6.4.4",
|
||||
"endstop_invert": "6.4.1",
|
||||
"express_k10": "8.0.0",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"ota_update_hour": "8.2.3",
|
||||
"rpi_led_control": "6.4.4",
|
||||
"sensors": "6.3.0",
|
||||
"update_resource": "10.0.0",
|
||||
"use_update_channel": "6.4.12",
|
||||
"variables": "8.0.0"
|
||||
}
|
||||
|
|
22
README.md
22
README.md
|
@ -1,19 +1,17 @@
|
|||
# Build status
|
||||
| Master Build Status | Staging Build Status |
|
||||
| :---: | :---: |
|
||||
| [![Master Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/master.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/master) | [![Staging Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging) |
|
||||
---
|
||||
|
||||
<!-- DON'T CHANGE THE TEXT BELOW. It is used in documentation links. -->
|
||||
# :floppy_disk: Latest OS Image Downloads
|
||||
<!-- DON'T CHANGE THE TEXT ABOVE. It is used in documentation links. -->
|
||||
|
||||
Download the version of FarmBot OS that corresponds to the FarmBot kit and computer you have:
|
||||
The FarmBot OS release page has moved to [my.farm.bot/os](https://my.farm.bot/os)
|
||||
|
||||
| FarmBot Kit | Computer | Download Link |
|
||||
| --- | --- | --- |
|
||||
| Genesis v1.2, Genesis v1.3, Genesis v1.4, Genesis XL v1.4 | Raspberry Pi 3 | [Download FBOS](https://github.com/FarmBot/farmbot_os/releases/download/v9.0.0/farmbot-rpi3-9.0.0.img) |
|
||||
| Express v1.0, Express XL v1.0 | Raspberry Pi Zero W | Coming soon |
|
||||
Old versions of FarmBot OS can be found [here](https://github.com/FarmBot/farmbot_os/releases). Please note that [FarmBot does not provide support for old versions of FBOS](https://software.farm.bot/docs/support-policy).
|
||||
|
||||
---
|
||||
|
||||
## Build status
|
||||
| Master Build Status | Staging Build Status | Test Coverage |
|
||||
| :---: | :---: | :---: |
|
||||
| [![Master Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/master.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/master) | [![Staging Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging) | [![codecov](https://codecov.io/gh/FarmBot/farmbot_os/branch/staging/graph/badge.svg)](https://codecov.io/gh/FarmBot/farmbot_os) |
|
||||
|
||||
---
|
||||
|
||||
|
@ -35,6 +33,8 @@ Installation should be fairly straight forward, you will need a computer for thi
|
|||
1. download the [latest release file, available for download here](#floppy_disk-latest-os-image-downloads).
|
||||
0. ```dd if=</path/to/file> of=/dev/<sddevice> bs=4``` or use [Etcher](https://etcher.io/).
|
||||
|
||||
---
|
||||
|
||||
## Running
|
||||
_Refer to the [software documentation Configurator page](https://software.farm.bot/docs/configurator) for more detailed instructions._
|
||||
|
||||
|
|
|
@ -24,3 +24,18 @@ This release uses an improved Farmware API:
|
|||
* If you are a Farmware developer using Farmware Tools (`import farmware_tools`), the reinstalled Farmware should continue working as before. If you have authored a Farmware that does not use the package, you will need to replace any FarmBot device communication in your Farmware to use the `farmware_tools` package.
|
||||
|
||||
* See the [Farmware developer documentation](https://developer.farm.bot/docs/farmware) for more information.
|
||||
|
||||
# v9
|
||||
|
||||
FarmBot OS v8+ uses an improved Farmware API. See the [Farmware developer documentation](https://developer.farm.bot/docs/farmware) for more information.
|
||||
|
||||
# v10
|
||||
|
||||
FarmBot OS v10 features an improved *Mark As* step. If you have previously added *Mark As* steps to sequences, you will need to update them before they can be executed by FarmBot:
|
||||
* Open any sequences with a caution icon next to the name.
|
||||
* Click the `CONVERT` button in each old *Mark As* step.
|
||||
* Save the sequence.
|
||||
* If you have auto-sync disabled, press `SYNC NOW` once all sequences have been updated.
|
||||
* Verify that any events using the updated sequences are running as expected.
|
||||
|
||||
FarmBot OS auto-update was disabled prior to this release.
|
||||
|
|
|
@ -6,6 +6,30 @@ This document will act as an index to available documentation.
|
|||
|
||||
* [FarmBot Source Code common terms](/docs/glossary.md)
|
||||
|
||||
## Cheat Sheet
|
||||
|
||||
**Create a *.fw file from local repo (RPi Zero):**
|
||||
|
||||
```sh
|
||||
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix deps.get
|
||||
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix firmware
|
||||
sudo fwup farmbot_os/_build/rpi/rpi_dev/nerves/images/farmbot.fw
|
||||
```
|
||||
|
||||
**Create a *.fw file from local repo (RPi v3):**
|
||||
|
||||
```sh
|
||||
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix deps.get
|
||||
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix firmware
|
||||
sudo fwup farmbot_os/_build/rpi3/rpi3_dev/nerves/images/farmbot.fw
|
||||
```
|
||||
|
||||
**Create or Update the Nerves System:**
|
||||
|
||||
Please see the official [Nerves documentation on "Nerves Systems"](https://hexdocs.pm/nerves/0.4.0/systems.html).
|
||||
|
||||
HINT: You may want to [develop the system locally](https://stackoverflow.com/a/28189056/1064917)
|
||||
|
||||
## Hardware specifics
|
||||
|
||||
Most FarmBot development/testing is done on a standard desktop PC.
|
||||
|
|
|
@ -10,7 +10,7 @@ Publishing a FarmBotOS release requires coordination of a few different systems.
|
|||
|
||||
## Legacy Release System
|
||||
|
||||
The legacy system is somewhat simpiler. It goes as follows:
|
||||
The legacy system is somewhat simpler. It goes as follows:
|
||||
|
||||
### Pull request into `master` branch
|
||||
|
||||
|
@ -54,7 +54,7 @@ Beta releases are constructed by creating a tag off of the `staging` branch.
|
|||
|
||||
## NervesHub System
|
||||
|
||||
The NervesHub system is simpiler to use, but more complex to setup.
|
||||
The NervesHub system is simpler to use, but more complex to setup.
|
||||
|
||||
### User registration
|
||||
|
||||
|
|
|
@ -1,14 +1,69 @@
|
|||
# Publishing OTAs
|
||||
|
||||
## Beta
|
||||
## Beta OTA channel
|
||||
|
||||
Publish an OTA to the `beta` channel can be done by:
|
||||
Beta updates are simply tags matching the following semver string:
|
||||
|
||||
```
|
||||
vMajor.Minor.Tiny-rcRC
|
||||
```
|
||||
|
||||
for example:
|
||||
|
||||
```
|
||||
v10.5.6-rc30
|
||||
```
|
||||
|
||||
To publish an OTA, just tag a release matching that
|
||||
string.
|
||||
|
||||
```bash
|
||||
cd $FARMBOT_OS_ROOT_DIRECTORY
|
||||
git checkout staging
|
||||
|
||||
# Ensure you don't accidentally publish local changes
|
||||
# that have not gone through CI:
|
||||
git fetch --all
|
||||
git reset --hard origin/staging
|
||||
|
||||
# update the CHANGELOG, but DO NOT put the `rc`
|
||||
# on the semver string.
|
||||
$EDITOR CHANGELOG.md
|
||||
|
||||
echo 1.2.3-rc4 > VERSION
|
||||
|
||||
git add -A
|
||||
git commit -am "Release v10.5.6-rc30"
|
||||
git tag v1.2.3-rc4
|
||||
git push origin v1.2.3-rc4
|
||||
```
|
||||
|
||||
or call the helper script:
|
||||
`./scripts/release_candidate.sh`
|
||||
|
||||
### NOTE about release candidate script
|
||||
|
||||
the helper script only **increments** the
|
||||
RC version. Calling the `release-candidate` script
|
||||
from a non rc version will fail. Example:
|
||||
|
||||
This will fail:
|
||||
|
||||
```bash
|
||||
$ cat VERSION
|
||||
10.5.6
|
||||
./scripts/release_candidate.sh
|
||||
```
|
||||
|
||||
## QA
|
||||
This will succeed:
|
||||
|
||||
```bash
|
||||
$ cat VERSION
|
||||
10.5.6-rc44
|
||||
./scripts/release_candidate.sh
|
||||
```
|
||||
|
||||
## QA OTA channel
|
||||
|
||||
Publish an OTA to the `qa` channel can be done by pushing a new branch
|
||||
to github with `qa/` prefix.
|
||||
|
@ -18,17 +73,27 @@ git checkout -b qa/<some-name>
|
|||
git push origin qa/<some-name>
|
||||
```
|
||||
|
||||
## Production
|
||||
|
||||
Publish an OTA to the `stable` channel can be done by:
|
||||
or to build a QA image from an existing branch:
|
||||
|
||||
```bash
|
||||
git checkout -b some-feature
|
||||
git commit -am "build out some feature"
|
||||
git push origin some-feature some-feature:qa/some-featuer
|
||||
```
|
||||
|
||||
## Stable OTA channel
|
||||
|
||||
Publish an OTA to the `stable` OTA channel can be
|
||||
done by pushing anything to the master branch:
|
||||
|
||||
```bash
|
||||
git checkout -b rel-<version>
|
||||
# update VERSION
|
||||
echo $NEW_VERSION > VERSION
|
||||
# update CHANGELOG.md
|
||||
# update README.md
|
||||
git commit -am "Release v<version>"
|
||||
git push origin rel-<version>
|
||||
$EDITOR CHANGELOG.md
|
||||
git checkout -b rel-$(cat VERSION)
|
||||
git commit -am 'Release v$(cat VERSION)'
|
||||
git push origin rel-$(cat VERSION)
|
||||
# open pull request
|
||||
# merge pull request
|
||||
# publish release once CI has completed
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[
|
||||
line_length: 80,
|
||||
inputs: ["*.{ex,exs}", "{config,priv,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
||||
|
|
|
@ -25,3 +25,4 @@ farmbot_ng-*.tar
|
|||
*.sqlite3
|
||||
*.so
|
||||
*.hex
|
||||
*.coverdata
|
|
@ -1,9 +1,4 @@
|
|||
use Mix.Config
|
||||
|
||||
if Mix.env() == :test do
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: Farmbot.TestSupport.CeleryScript.TestSysCalls
|
||||
else
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
end
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{
|
||||
"skip_files": [
|
||||
"lib/farmbot_celery_script/compiler/tools.ex"
|
||||
]
|
||||
],
|
||||
"minimum_coverage": 53,
|
||||
"treat_no_relevant_lines_as_covered": true
|
||||
}
|
||||
|
|
|
@ -22,13 +22,14 @@
|
|||
{
|
||||
"name": "ALLOWED_MESSAGE_TYPES",
|
||||
"allowed_values": [
|
||||
"success",
|
||||
"assertion",
|
||||
"busy",
|
||||
"warn",
|
||||
"debug",
|
||||
"error",
|
||||
"info",
|
||||
"fun",
|
||||
"debug"
|
||||
"info",
|
||||
"success",
|
||||
"warn"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -55,6 +56,15 @@
|
|||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ALLOWED_ASSERTION_TYPES",
|
||||
"allowed_values": [
|
||||
"abort",
|
||||
"recover",
|
||||
"abort_recover",
|
||||
"continue"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AllowedPinTypes",
|
||||
"allowed_values": [
|
||||
|
@ -89,10 +99,10 @@
|
|||
"name": "LegalSequenceKind",
|
||||
"allowed_values": [
|
||||
"_if",
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
@ -110,7 +120,6 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -118,6 +127,7 @@
|
|||
"take_photo",
|
||||
"toggle_pin",
|
||||
"update_farmware",
|
||||
"update_resource",
|
||||
"wait",
|
||||
"write_pin",
|
||||
"zero"
|
||||
|
@ -207,7 +217,8 @@
|
|||
"planned",
|
||||
"planted",
|
||||
"harvested",
|
||||
"sprouted"
|
||||
"sprouted",
|
||||
"removed"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -215,24 +226,18 @@
|
|||
"allowed_values": [
|
||||
"GenericPointer",
|
||||
"ToolSlot",
|
||||
"Plant"
|
||||
"Plant",
|
||||
"Weed"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "resource_type",
|
||||
"allowed_values": [
|
||||
"Device",
|
||||
"FarmEvent",
|
||||
"Image",
|
||||
"Log",
|
||||
"Peripheral",
|
||||
"Plant",
|
||||
"Point",
|
||||
"Regimen",
|
||||
"Sequence",
|
||||
"Tool",
|
||||
"Plant",
|
||||
"ToolSlot",
|
||||
"User",
|
||||
"Weed",
|
||||
"GenericPointer"
|
||||
]
|
||||
},
|
||||
|
@ -262,11 +267,13 @@
|
|||
"z",
|
||||
"pin_type",
|
||||
"pointer_id",
|
||||
"point_group_id",
|
||||
"pointer_type",
|
||||
"pin_mode",
|
||||
"sequence_id",
|
||||
"lhs",
|
||||
"op",
|
||||
"priority",
|
||||
"channel_name",
|
||||
"message_type",
|
||||
"tool_id",
|
||||
|
@ -274,19 +281,22 @@
|
|||
"axis",
|
||||
"message",
|
||||
"speed",
|
||||
"resource_type"
|
||||
"resource_type",
|
||||
"assertion_type",
|
||||
"lua",
|
||||
"resource"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LegalKindString",
|
||||
"allowed_values": [
|
||||
"Assertion",
|
||||
"If",
|
||||
"Calibrate",
|
||||
"ChangeOwnership",
|
||||
"Channel",
|
||||
"CheckUpdates",
|
||||
"Coordinate",
|
||||
"DumpInfo",
|
||||
"EmergencyLock",
|
||||
"EmergencyUnlock",
|
||||
"ExecuteScript",
|
||||
|
@ -332,7 +342,10 @@
|
|||
"MoveAbsolute",
|
||||
"WritePin",
|
||||
"ReadPin",
|
||||
"ResourceUpdate"
|
||||
"ResourceUpdate",
|
||||
"Resource",
|
||||
"UpdateResource",
|
||||
"PointGroup"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
@ -395,6 +408,10 @@
|
|||
{
|
||||
"tag": "identifier",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"tag": "point_group",
|
||||
"name": "point_group"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -626,6 +643,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "point_group_id",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "integer",
|
||||
"name": "Integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pointer_type",
|
||||
"allowed_values": [
|
||||
|
@ -675,6 +701,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "priority",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "integer",
|
||||
"name": "Integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "channel_name",
|
||||
"allowed_values": [
|
||||
|
@ -746,9 +781,53 @@
|
|||
"name": "resource_type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "assertion_type",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "ALLOWED_ASSERTION_TYPES",
|
||||
"name": "ALLOWED_ASSERTION_TYPES"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "lua",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "string",
|
||||
"name": "String"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "resource",
|
||||
"allowed_values": [
|
||||
{
|
||||
"tag": "identifier",
|
||||
"name": "identifier"
|
||||
},
|
||||
{
|
||||
"tag": "resource",
|
||||
"name": "resource"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"allowed_args": [
|
||||
"assertion_type",
|
||||
"_then",
|
||||
"lua"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "assertion",
|
||||
"tags": [
|
||||
"*"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"lhs",
|
||||
|
@ -832,18 +911,6 @@
|
|||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [],
|
||||
"allowed_body_types": [],
|
||||
"name": "dump_info",
|
||||
"tags": [
|
||||
"function",
|
||||
"network_user",
|
||||
"disk_user",
|
||||
"api_writer"
|
||||
],
|
||||
"docs": "Sends an info dump to server administrators for troubleshooting."
|
||||
},
|
||||
{
|
||||
"allowed_args": [],
|
||||
"allowed_body_types": [],
|
||||
|
@ -1164,13 +1231,14 @@
|
|||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"label"
|
||||
"label",
|
||||
"priority"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
@ -1189,7 +1257,7 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"update_resource",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -1239,10 +1307,10 @@
|
|||
"locals"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"assertion",
|
||||
"calibrate",
|
||||
"change_ownership",
|
||||
"check_updates",
|
||||
"dump_info",
|
||||
"emergency_lock",
|
||||
"emergency_unlock",
|
||||
"execute",
|
||||
|
@ -1261,7 +1329,7 @@
|
|||
"read_status",
|
||||
"reboot",
|
||||
"remove_farmware",
|
||||
"resource_update",
|
||||
"update_resource",
|
||||
"send_message",
|
||||
"set_servo_angle",
|
||||
"set_user_env",
|
||||
|
@ -1474,6 +1542,45 @@
|
|||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"resource_type",
|
||||
"resource_id"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "resource",
|
||||
"tags": [
|
||||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"resource"
|
||||
],
|
||||
"allowed_body_types": [
|
||||
"pair"
|
||||
],
|
||||
"name": "update_resource",
|
||||
"tags": [
|
||||
"function",
|
||||
"api_writer",
|
||||
"network_user"
|
||||
],
|
||||
"docs": ""
|
||||
},
|
||||
{
|
||||
"allowed_args": [
|
||||
"point_group_id"
|
||||
],
|
||||
"allowed_body_types": [],
|
||||
"name": "point_group",
|
||||
"tags": [
|
||||
"data",
|
||||
"list_like"
|
||||
],
|
||||
"docs": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -77,7 +77,8 @@ defmodule FarmbotCeleryScript.AST do
|
|||
end
|
||||
|
||||
@spec new(atom, map, [map]) :: t()
|
||||
def new(kind, args, body, comment \\ nil, meta \\ nil) when is_map(args) and is_list(body) do
|
||||
def new(kind, args, body, comment \\ nil, meta \\ nil)
|
||||
when is_map(args) and is_list(body) do
|
||||
%AST{
|
||||
kind: String.to_atom(to_string(kind)),
|
||||
args: args,
|
||||
|
|
|
@ -5,10 +5,35 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
|||
|
||||
alias FarmbotCeleryScript.AST
|
||||
|
||||
@doc """
|
||||
Create an empty AST WITH ARG SET TO `nil`.
|
||||
|
||||
iex> new()
|
||||
%FarmbotCeleryScript.AST{
|
||||
args: nil,
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: nil,
|
||||
meta: nil
|
||||
}
|
||||
"""
|
||||
def new do
|
||||
%AST{body: []}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a new AST to work with. Strings `kind`s are
|
||||
converted to symbols.
|
||||
|
||||
iex> new("foo")
|
||||
%FarmbotCeleryScript.AST{
|
||||
args: %{},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :foo,
|
||||
meta: nil
|
||||
}
|
||||
"""
|
||||
def new(kind, args \\ %{}, body \\ []) do
|
||||
AST.new(kind, Map.new(args), body)
|
||||
end
|
||||
|
@ -19,65 +44,153 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
|||
|
||||
def read_pin(%AST{} = ast, pin_number, pin_mode) do
|
||||
ast
|
||||
|> add_body_node(new(:read_pin, %{pin_number: pin_number, pin_mode: pin_mode}))
|
||||
|> add_body_node(
|
||||
new(:read_pin, %{pin_number: pin_number, pin_mode: pin_mode})
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> set_pin_io_mode(13, 1)).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
kind: :set_pin_io_mode,
|
||||
args: %{ pin_io_mode: 1, pin_number: 13 },
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil
|
||||
}]
|
||||
"""
|
||||
def set_pin_io_mode(%AST{} = ast, pin_number, pin_io_mode) do
|
||||
ast
|
||||
|> add_body_node(new(:set_pin_io_mode, %{pin_number: pin_number, pin_io_mode: pin_io_mode}))
|
||||
end
|
||||
|
||||
def dump_info(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:dump_info))
|
||||
args = %{pin_number: pin_number, pin_io_mode: pin_io_mode}
|
||||
ast |> add_body_node(new(:set_pin_io_mode, args))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> emergency_lock()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :emergency_lock
|
||||
}]
|
||||
"""
|
||||
def emergency_lock(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:emergency_lock))
|
||||
ast |> add_body_node(new(:emergency_lock))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> emergency_unlock()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :emergency_unlock
|
||||
}]
|
||||
"""
|
||||
def emergency_unlock(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:emergency_unlock))
|
||||
ast |> add_body_node(new(:emergency_unlock))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> read_status()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :read_status
|
||||
}]
|
||||
"""
|
||||
def read_status(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:read_status))
|
||||
ast |> add_body_node(new(:read_status))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> power_off()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :power_off
|
||||
}]
|
||||
"""
|
||||
def power_off(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:power_off))
|
||||
ast |> add_body_node(new(:power_off))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> reboot()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :reboot
|
||||
}]
|
||||
"""
|
||||
def reboot(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:reboot))
|
||||
ast |> add_body_node(new(:reboot))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> sync()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :sync
|
||||
}]
|
||||
"""
|
||||
def sync(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:sync))
|
||||
ast |> add_body_node(new(:sync))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> take_photo()).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
body: [],
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{},
|
||||
kind: :take_photo
|
||||
}]
|
||||
"""
|
||||
def take_photo(%AST{} = ast) do
|
||||
ast
|
||||
|> add_body_node(new(:take_photo))
|
||||
ast |> add_body_node(new(:take_photo))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> flash_firmware("arduino")).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
kind: :flash_firmware,
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{package: "arduino"},
|
||||
body: [],
|
||||
}]
|
||||
"""
|
||||
def flash_firmware(%AST{} = ast, package) when is_binary(package) do
|
||||
ast
|
||||
|> add_body_node(new(:flash_firmware, %{package: package}))
|
||||
ast |> add_body_node(new(:flash_firmware, %{package: package}))
|
||||
end
|
||||
|
||||
@doc """
|
||||
iex> (new() |> rpc_request("x") |> factory_reset("arduino")).body
|
||||
[%FarmbotCeleryScript.AST{
|
||||
kind: :factory_reset,
|
||||
comment: nil,
|
||||
meta: nil,
|
||||
args: %{package: "arduino"},
|
||||
body: [],
|
||||
}]
|
||||
"""
|
||||
def factory_reset(%AST{} = ast, package) do
|
||||
ast |> add_body_node(new(:factory_reset, %{package: package}))
|
||||
end
|
||||
|
||||
def add_body_node(%AST{body: body} = ast, %AST{} = body_node) do
|
||||
%{ast | body: body ++ [body_node]}
|
||||
end
|
||||
|
||||
def factory_reset(%AST{} = ast, package) do
|
||||
ast
|
||||
|> add_body_node(new(:factory_reset, %{package: package}))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,17 +11,10 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
Compiler.IdentifierSanitizer
|
||||
}
|
||||
|
||||
@doc "Sets debug mode for the compiler"
|
||||
def debug_mode(bool \\ true) do
|
||||
old = Application.get_env(:farmbot_celery_script, __MODULE__, [])
|
||||
new = Keyword.put(old, :debug, bool)
|
||||
Application.put_env(:farmbot_celery_script, __MODULE__, new)
|
||||
bool
|
||||
end
|
||||
|
||||
@doc "Returns current debug mode value"
|
||||
def debug_mode?() do
|
||||
Application.get_env(:farmbot_celery_script, __MODULE__)[:debug] || false
|
||||
# Set this to `true` when debuging.
|
||||
false
|
||||
end
|
||||
|
||||
@valid_entry_points [:sequence, :rpc_request]
|
||||
|
@ -94,27 +87,28 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
defdelegate assertion(ast, env), to: Compiler.Assertion
|
||||
defdelegate calibrate(ast, env), to: Compiler.AxisControl
|
||||
defdelegate coordinate(ast, env), to: Compiler.DataControl
|
||||
defdelegate execute(ast, env), to: Compiler.Execute
|
||||
defdelegate execute_script(ast, env), to: Compiler.Farmware
|
||||
defdelegate execute(ast, env), to: Compiler.Execute
|
||||
defdelegate find_home(ast, env), to: Compiler.AxisControl
|
||||
defdelegate home(ast, env), to: Compiler.AxisControl
|
||||
defdelegate unquote(:_if)(ast, env), to: Compiler.If
|
||||
defdelegate install_first_party_farmware(ast, env), to: Compiler.Farmware
|
||||
defdelegate move_absolute(ast, env), to: Compiler.AxisControl
|
||||
defdelegate move_relative(ast, env), to: Compiler.AxisControl
|
||||
defdelegate named_pin(ast, env), to: Compiler.DataControl
|
||||
defdelegate point(ast, env), to: Compiler.DataControl
|
||||
defdelegate read_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate resource_update(ast, env), to: Compiler.DataControl
|
||||
defdelegate resource(ast, env), to: Compiler.DataControl
|
||||
defdelegate rpc_request(ast, env), to: Compiler.RPCRequest
|
||||
defdelegate sequence(ast, env), to: Compiler.Sequence
|
||||
defdelegate set_pin_io_mode(ast, env), to: Compiler.PinControl
|
||||
defdelegate set_servo_angle(ast, env), to: Compiler.PinControl
|
||||
defdelegate set_user_env(ast, env), to: Compiler.Farmware
|
||||
defdelegate take_photo(ast, env), to: Compiler.Farmware
|
||||
defdelegate tool(ast, env), to: Compiler.DataControl
|
||||
defdelegate toggle_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate tool(ast, env), to: Compiler.DataControl
|
||||
defdelegate unquote(:_if)(ast, env), to: Compiler.If
|
||||
defdelegate update_farmware(ast, env), to: Compiler.Farmware
|
||||
defdelegate update_resource(ast, env), to: Compiler.UpdateResource
|
||||
defdelegate variable_declaration(ast, env), to: Compiler.VariableDeclaration
|
||||
defdelegate write_pin(ast, env), to: Compiler.PinControl
|
||||
defdelegate zero(ast, env), to: Compiler.AxisControl
|
||||
|
@ -155,14 +149,20 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
end
|
||||
end
|
||||
|
||||
def send_message(%{args: %{message: msg, message_type: type}, body: channels}, env) do
|
||||
def send_message(
|
||||
%{args: %{message: msg, message_type: type}, body: channels},
|
||||
env
|
||||
) do
|
||||
# body gets turned into a list of atoms.
|
||||
# Example:
|
||||
# [{kind: "channel", args: {channel_name: "email"}}]
|
||||
# is turned into:
|
||||
# [:email]
|
||||
channels =
|
||||
Enum.map(channels, fn %{kind: :channel, args: %{channel_name: channel_name}} ->
|
||||
Enum.map(channels, fn %{
|
||||
kind: :channel,
|
||||
args: %{channel_name: channel_name}
|
||||
} ->
|
||||
String.to_atom(channel_name)
|
||||
end)
|
||||
|
||||
|
@ -218,7 +218,9 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
|
||||
def flash_firmware(%{args: %{package: package_name}}, env) do
|
||||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.flash_firmware(unquote(compile_ast(package_name, env)))
|
||||
FarmbotCeleryScript.SysCalls.flash_firmware(
|
||||
unquote(compile_ast(package_name, env))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -242,7 +244,9 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
|
||||
def factory_reset(%{args: %{package: package}}, env) do
|
||||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.factory_reset(unquote(compile_ast(package, env)))
|
||||
FarmbotCeleryScript.SysCalls.factory_reset(
|
||||
unquote(compile_ast(package, env))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -269,20 +273,14 @@ defmodule FarmbotCeleryScript.Compiler do
|
|||
end
|
||||
end
|
||||
|
||||
def dump_info(_, _env) do
|
||||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.dump_info()
|
||||
end
|
||||
end
|
||||
|
||||
defp print_compiled_code(compiled) do
|
||||
IO.puts("========")
|
||||
IO.puts("=== START ===")
|
||||
|
||||
compiled
|
||||
|> Macro.to_string()
|
||||
|> Code.format_string!()
|
||||
|> IO.puts()
|
||||
|
||||
IO.puts("========\n\n")
|
||||
IO.puts("=== END ===\n\n")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule FarmbotCeleryScript.Compiler.IdentifierSanitizer do
|
|||
@moduledoc """
|
||||
Responsible for ensuring variable names in Sequences are clean.
|
||||
This is done because identifiers are `unquote`d and the user controls
|
||||
the data inside them. To prevent things like
|
||||
the data inside them. To prevent things like
|
||||
`"System.cmd("rm -rf /*/**")"` being evaluated, all identifiers
|
||||
are sanitized by prepending a token and hashing the value.
|
||||
"""
|
||||
|
|
|
@ -80,7 +80,8 @@ defmodule FarmbotCeleryScript.Compiler.Utils do
|
|||
|
||||
var =
|
||||
quote location: :keep do
|
||||
{unquote(next_scope_var_name), unquote(Compiler.compile_ast(data_value, env))}
|
||||
{unquote(next_scope_var_name),
|
||||
unquote(Compiler.compile_ast(data_value, env))}
|
||||
end
|
||||
|
||||
compile_params_to_function_args(rest, env, [var | acc])
|
||||
|
@ -117,12 +118,19 @@ defmodule FarmbotCeleryScript.Compiler.Utils do
|
|||
|
||||
parent = Keyword.get(params, :parent, %{x: 100, y: 200, z: 300})
|
||||
"""
|
||||
def compile_param_declaration(%{args: %{label: var_name, default_value: default}}, env) do
|
||||
def compile_param_declaration(
|
||||
%{args: %{label: var_name, default_value: default}},
|
||||
env
|
||||
) do
|
||||
var_name = IdentifierSanitizer.to_variable(var_name)
|
||||
|
||||
quote location: :keep do
|
||||
unquote({var_name, env, __MODULE__}) =
|
||||
Keyword.get(params, unquote(var_name), unquote(Compiler.compile_ast(default, env)))
|
||||
Keyword.get(
|
||||
params,
|
||||
unquote(var_name),
|
||||
unquote(Compiler.compile_ast(default, env))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -152,11 +160,15 @@ defmodule FarmbotCeleryScript.Compiler.Utils do
|
|||
]
|
||||
}
|
||||
"""
|
||||
def compile_param_application(%{args: %{label: var_name, data_value: value}}, env) do
|
||||
def compile_param_application(
|
||||
%{args: %{label: var_name, data_value: value}},
|
||||
env
|
||||
) do
|
||||
var_name = IdentifierSanitizer.to_variable(var_name)
|
||||
|
||||
quote location: :keep do
|
||||
unquote({var_name, [], __MODULE__}) = unquote(Compiler.compile_ast(value, env))
|
||||
unquote({var_name, [], __MODULE__}) =
|
||||
unquote(Compiler.compile_ast(value, env))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -168,13 +180,16 @@ defmodule FarmbotCeleryScript.Compiler.Utils do
|
|||
end)
|
||||
end
|
||||
|
||||
def add_sequence_init_and_complete_logs(steps, sequence_name) when is_binary(sequence_name) do
|
||||
def add_sequence_init_and_complete_logs(steps, sequence_name)
|
||||
when is_binary(sequence_name) do
|
||||
# This looks really weird because of the logs before and
|
||||
# after the compiled steps
|
||||
List.flatten([
|
||||
quote do
|
||||
fn ->
|
||||
FarmbotCeleryScript.SysCalls.sequence_init_log("Starting #{unquote(sequence_name)}")
|
||||
FarmbotCeleryScript.SysCalls.sequence_init_log(
|
||||
"Starting #{unquote(sequence_name)}"
|
||||
)
|
||||
end
|
||||
end,
|
||||
steps,
|
||||
|
@ -201,7 +216,9 @@ defmodule FarmbotCeleryScript.Compiler.Utils do
|
|||
fn _ ->
|
||||
[
|
||||
fn ->
|
||||
FarmbotCeleryScript.SysCalls.sequence_init_log("Starting #{unquote(sequence_name)}")
|
||||
FarmbotCeleryScript.SysCalls.sequence_init_log(
|
||||
"Starting #{unquote(sequence_name)}"
|
||||
)
|
||||
end
|
||||
]
|
||||
end
|
||||
|
|
|
@ -4,7 +4,11 @@ defmodule FarmbotCeleryScript.Compiler.Assertion do
|
|||
@doc "`Assert` is a internal node useful for self testing."
|
||||
def assertion(
|
||||
%{
|
||||
args: %{lua: expression, assertion_type: assertion_type, _then: then_ast},
|
||||
args: %{
|
||||
lua: expression,
|
||||
assertion_type: assertion_type,
|
||||
_then: then_ast
|
||||
},
|
||||
comment: comment
|
||||
},
|
||||
env
|
||||
|
@ -80,7 +84,10 @@ defmodule FarmbotCeleryScript.Compiler.Assertion do
|
|||
|
||||
then_block ++
|
||||
[
|
||||
FarmbotCeleryScript.Compiler.compile(%AST{kind: :abort, args: %{}}, [])
|
||||
FarmbotCeleryScript.Compiler.compile(
|
||||
%AST{kind: :abort, args: %{}},
|
||||
[]
|
||||
)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
||||
alias FarmbotCeleryScript.Compiler
|
||||
# Compiles move_absolute
|
||||
def move_absolute(%{args: %{location: location, offset: offset, speed: speed}}, env) do
|
||||
def move_absolute(
|
||||
%{args: %{location: location, offset: offset, speed: speed}},
|
||||
env
|
||||
) do
|
||||
quote location: :keep do
|
||||
# Extract the location arg
|
||||
with %{x: locx, y: locy, z: locz} = unquote(Compiler.compile_ast(location, env)),
|
||||
with %{x: locx, y: locy, z: locz} =
|
||||
unquote(Compiler.compile_ast(location, env)),
|
||||
# Extract the offset arg
|
||||
%{x: offx, y: offy, z: offz} = unquote(Compiler.compile_ast(offset, env)) do
|
||||
%{x: offx, y: offy, z: offz} =
|
||||
unquote(Compiler.compile_ast(offset, env)) do
|
||||
# Subtract the location from offset.
|
||||
# Note: list syntax here for readability.
|
||||
[x, y, z] = [
|
||||
|
@ -18,7 +23,11 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
x_str = FarmbotCeleryScript.FormatUtil.format_float(x)
|
||||
y_str = FarmbotCeleryScript.FormatUtil.format_float(y)
|
||||
z_str = FarmbotCeleryScript.FormatUtil.format_float(z)
|
||||
FarmbotCeleryScript.SysCalls.log("Moving to (#{x_str}, #{y_str}, #{z_str})", true)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.log(
|
||||
"Moving to (#{x_str}, #{y_str}, #{z_str})",
|
||||
true
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.move_absolute(
|
||||
x,
|
||||
|
@ -36,9 +45,12 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
with locx when is_number(locx) <- unquote(Compiler.compile_ast(x, env)),
|
||||
locy when is_number(locy) <- unquote(Compiler.compile_ast(y, env)),
|
||||
locz when is_number(locz) <- unquote(Compiler.compile_ast(z, env)),
|
||||
curx when is_number(curx) <- FarmbotCeleryScript.SysCalls.get_current_x(),
|
||||
cury when is_number(cury) <- FarmbotCeleryScript.SysCalls.get_current_y(),
|
||||
curz when is_number(curz) <- FarmbotCeleryScript.SysCalls.get_current_z() do
|
||||
curx when is_number(curx) <-
|
||||
FarmbotCeleryScript.SysCalls.get_current_x(),
|
||||
cury when is_number(cury) <-
|
||||
FarmbotCeleryScript.SysCalls.get_current_y(),
|
||||
curz when is_number(curz) <-
|
||||
FarmbotCeleryScript.SysCalls.get_current_z() do
|
||||
# Combine them
|
||||
x = locx + curx
|
||||
y = locy + cury
|
||||
|
@ -77,8 +89,13 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
# compiles find_home
|
||||
def find_home(%{args: %{axis: axis}}, env) do
|
||||
quote location: :keep do
|
||||
with axis when axis in ["x", "y", "z"] <- unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log("Finding home on the #{String.upcase(axis)} axis", true)
|
||||
with axis when axis in ["x", "y", "z"] <-
|
||||
unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log(
|
||||
"Finding home on the #{String.upcase(axis)} axis",
|
||||
true
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.find_home(axis)
|
||||
else
|
||||
{:error, reason} ->
|
||||
|
@ -92,7 +109,8 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.log("Going to home on all axes", true)
|
||||
|
||||
with speed when is_number(speed) <- unquote(Compiler.compile_ast(speed, env)),
|
||||
with speed when is_number(speed) <-
|
||||
unquote(Compiler.compile_ast(speed, env)),
|
||||
:ok <- FarmbotCeleryScript.SysCalls.home("z", speed),
|
||||
:ok <- FarmbotCeleryScript.SysCalls.home("y", speed) do
|
||||
FarmbotCeleryScript.SysCalls.home("x", speed)
|
||||
|
@ -103,9 +121,15 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
# compiles home
|
||||
def home(%{args: %{axis: axis, speed: speed}}, env) do
|
||||
quote location: :keep do
|
||||
with axis when axis in ["x", "y", "z"] <- unquote(Compiler.compile_ast(axis, env)),
|
||||
speed when is_number(speed) <- unquote(Compiler.compile_ast(speed, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log("Going to home on the #{String.upcase(axis)} axis", true)
|
||||
with axis when axis in ["x", "y", "z"] <-
|
||||
unquote(Compiler.compile_ast(axis, env)),
|
||||
speed when is_number(speed) <-
|
||||
unquote(Compiler.compile_ast(speed, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log(
|
||||
"Going to home on the #{String.upcase(axis)} axis",
|
||||
true
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.home(axis, speed)
|
||||
else
|
||||
{:error, reason} ->
|
||||
|
@ -129,8 +153,13 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
# compiles zero
|
||||
def zero(%{args: %{axis: axis}}, env) do
|
||||
quote location: :keep do
|
||||
with axis when axis in ["x", "y", "z"] <- unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log("Zeroing the #{String.upcase(axis)} axis", true)
|
||||
with axis when axis in ["x", "y", "z"] <-
|
||||
unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log(
|
||||
"Zeroing the #{String.upcase(axis)} axis",
|
||||
true
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.zero(axis)
|
||||
else
|
||||
{:error, reason} ->
|
||||
|
@ -157,8 +186,13 @@ defmodule FarmbotCeleryScript.Compiler.AxisControl do
|
|||
# compiles calibrate
|
||||
def calibrate(%{args: %{axis: axis}}, env) do
|
||||
quote location: :keep do
|
||||
with axis when axis in ["x", "y", "z"] <- unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log("Calibrating the #{String.upcase(axis)} axis", true)
|
||||
with axis when axis in ["x", "y", "z"] <-
|
||||
unquote(Compiler.compile_ast(axis, env)) do
|
||||
FarmbotCeleryScript.SysCalls.log(
|
||||
"Calibrating the #{String.upcase(axis)} axis",
|
||||
true
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.SysCalls.calibrate(axis)
|
||||
else
|
||||
{:error, reason} ->
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.DataControl do
|
||||
alias FarmbotCeleryScript.Compiler
|
||||
|
||||
def resource(ast, _env) do
|
||||
IO.puts("======")
|
||||
IO.inspect(ast)
|
||||
# %FarmbotCeleryScript.AST{
|
||||
# args: %{resource_id: 0, resource_type: "Device"},
|
||||
# body: [],
|
||||
# comment: nil,
|
||||
# kind: :resource,
|
||||
# meta: nil
|
||||
# }
|
||||
raise "TODO: Pull resource from DB?"
|
||||
end
|
||||
|
||||
# compiles coordinate
|
||||
# Coordinate should return a vec3
|
||||
def coordinate(%{args: %{x: x, y: y, z: z}}, env) do
|
||||
|
@ -40,27 +53,4 @@ defmodule FarmbotCeleryScript.Compiler.DataControl do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
def resource_update(
|
||||
%{args: %{resource_type: kind, resource_id: id, label: label, value: value}, body: body},
|
||||
env
|
||||
) do
|
||||
initial = %{label => value}
|
||||
# Technically now body isn't supported by this node.
|
||||
extra =
|
||||
Map.new(body, fn %{args: %{label: label, data_value: value}} ->
|
||||
{label, value}
|
||||
end)
|
||||
|
||||
# Make sure the initial stuff higher most priority
|
||||
params = Map.merge(extra, initial)
|
||||
|
||||
quote do
|
||||
FarmbotCeleryScript.SysCalls.resource_update(
|
||||
unquote(Compiler.compile_ast(kind, env)),
|
||||
unquote(Compiler.compile_ast(id, env)),
|
||||
unquote(Macro.escape(params))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,8 @@ defmodule FarmbotCeleryScript.Compiler.Execute do
|
|||
loop_parameter_appl_ast =
|
||||
Enum.find_value(parameter_applications, fn
|
||||
# check if this parameter_application is a iterable type
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: kind}}} = iterable
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: kind}}} =
|
||||
iterable
|
||||
when kind in @iterables ->
|
||||
iterable
|
||||
|
||||
|
@ -38,7 +39,10 @@ defmodule FarmbotCeleryScript.Compiler.Execute do
|
|||
%FarmbotCeleryScript.AST{kind: :sequence} = celery_ast ->
|
||||
celery_args =
|
||||
celery_ast.args
|
||||
|> Map.put(:sequence_name, celery_ast.args[:name] || celery_ast.meta[:sequence_name])
|
||||
|> Map.put(
|
||||
:sequence_name,
|
||||
celery_ast.args[:name] || celery_ast.meta[:sequence_name]
|
||||
)
|
||||
|> Map.put(:locals, %{
|
||||
celery_ast.args.locals
|
||||
| body: celery_ast.args.locals.body ++ unquote(param_appls)
|
||||
|
@ -53,13 +57,20 @@ defmodule FarmbotCeleryScript.Compiler.Execute do
|
|||
end
|
||||
end
|
||||
|
||||
def compile_execute(%{args: %{sequence_id: id}, body: parameter_applications}, env) do
|
||||
def compile_execute(
|
||||
%{args: %{sequence_id: id}, body: parameter_applications},
|
||||
env
|
||||
) do
|
||||
quote location: :keep do
|
||||
# We have to lookup the sequence by it's id.
|
||||
case FarmbotCeleryScript.SysCalls.get_sequence(unquote(id)) do
|
||||
%FarmbotCeleryScript.AST{} = ast ->
|
||||
# compile the ast
|
||||
env = unquote(compile_params_to_function_args(parameter_applications, env))
|
||||
env =
|
||||
unquote(
|
||||
compile_params_to_function_args(parameter_applications, env)
|
||||
)
|
||||
|
||||
FarmbotCeleryScript.Compiler.compile(ast, env)
|
||||
|
||||
error ->
|
||||
|
|
|
@ -30,7 +30,10 @@ defmodule FarmbotCeleryScript.Compiler.Farmware do
|
|||
kvs =
|
||||
Enum.map(pairs, fn %{kind: :pair, args: %{label: key, value: value}} ->
|
||||
quote location: :keep do
|
||||
FarmbotCeleryScript.SysCalls.set_user_env(unquote(key), unquote(value))
|
||||
FarmbotCeleryScript.SysCalls.set_user_env(
|
||||
unquote(key),
|
||||
unquote(value)
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
|
|
|
@ -4,7 +4,15 @@ defmodule FarmbotCeleryScript.Compiler.If do
|
|||
|
||||
# Compiles an if statement.
|
||||
def unquote(:_if)(
|
||||
%{args: %{_then: then_ast, _else: else_ast, lhs: lhs_ast, op: op, rhs: rhs}},
|
||||
%{
|
||||
args: %{
|
||||
_then: then_ast,
|
||||
_else: else_ast,
|
||||
lhs: lhs_ast,
|
||||
op: op,
|
||||
rhs: rhs
|
||||
}
|
||||
},
|
||||
env
|
||||
) do
|
||||
rhs = Compiler.compile_ast(rhs, env)
|
||||
|
@ -30,7 +38,10 @@ defmodule FarmbotCeleryScript.Compiler.If do
|
|||
|
||||
"pin" <> pin ->
|
||||
quote [location: :keep],
|
||||
do: FarmbotCeleryScript.SysCalls.read_cached_pin(unquote(String.to_integer(pin)))
|
||||
do:
|
||||
FarmbotCeleryScript.SysCalls.read_cached_pin(
|
||||
unquote(String.to_integer(pin))
|
||||
)
|
||||
|
||||
# Named pin has two intents here
|
||||
# in this case we want to read the named pin.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.PinControl do
|
||||
alias FarmbotCeleryScript.Compiler
|
||||
# compiles write_pin
|
||||
def write_pin(%{args: %{pin_number: num, pin_mode: mode, pin_value: value}}, env) do
|
||||
def write_pin(
|
||||
%{args: %{pin_number: num, pin_mode: mode, pin_value: value}},
|
||||
env
|
||||
) do
|
||||
quote location: :keep do
|
||||
pin = unquote(Compiler.compile_ast(num, env))
|
||||
mode = unquote(Compiler.compile_ast(mode, env))
|
||||
|
@ -23,7 +26,10 @@ defmodule FarmbotCeleryScript.Compiler.PinControl do
|
|||
end
|
||||
|
||||
# compiles set_servo_angle
|
||||
def set_servo_angle(%{args: %{pin_number: pin_number, pin_value: pin_value}}, env) do
|
||||
def set_servo_angle(
|
||||
%{args: %{pin_number: pin_number, pin_value: pin_value}},
|
||||
env
|
||||
) do
|
||||
quote location: :keep do
|
||||
pin = unquote(Compiler.compile_ast(pin_number, env))
|
||||
angle = unquote(Compiler.compile_ast(pin_value, env))
|
||||
|
@ -33,7 +39,10 @@ defmodule FarmbotCeleryScript.Compiler.PinControl do
|
|||
end
|
||||
|
||||
# compiles set_pin_io_mode
|
||||
def set_pin_io_mode(%{args: %{pin_number: pin_number, pin_io_mode: mode}}, env) do
|
||||
def set_pin_io_mode(
|
||||
%{args: %{pin_number: pin_number, pin_io_mode: mode}},
|
||||
env
|
||||
) do
|
||||
quote location: :keep do
|
||||
pin = unquote(Compiler.compile_ast(pin_number, env))
|
||||
mode = unquote(Compiler.compile_ast(mode, env))
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.Sequence do
|
||||
import FarmbotCeleryScript.Compiler.Utils
|
||||
alias FarmbotCeleryScript.Compiler.IdentifierSanitizer
|
||||
|
||||
@iterables [:point_group, :every_point]
|
||||
|
||||
def sequence(%{args: %{locals: %{body: params_or_iterables}}} = ast, env) do
|
||||
# if there is an iterable AST here,
|
||||
# if there is an iterable AST here,
|
||||
# we need to compile _many_ sequences, not just one.
|
||||
|
||||
loop_parameter_appl_ast =
|
||||
iterable_ast =
|
||||
Enum.find_value(params_or_iterables, fn
|
||||
# check if this parameter_application is a iterable type
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: kind}}} = iterable
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: kind}}} =
|
||||
iterable
|
||||
when kind in @iterables ->
|
||||
iterable
|
||||
|
||||
|
@ -18,48 +20,42 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
false
|
||||
end)
|
||||
|
||||
if loop_parameter_appl_ast,
|
||||
do: compile_sequence_iterable(loop_parameter_appl_ast, ast, env),
|
||||
else: compile_sequence(ast, env)
|
||||
if iterable_ast do
|
||||
compile_sequence_iterable(iterable_ast, ast, env)
|
||||
else
|
||||
compile_sequence(ast, env)
|
||||
end
|
||||
end
|
||||
|
||||
def compile_sequence_iterable(
|
||||
loop_parameter_appl_ast,
|
||||
%{args: %{locals: %{body: params} = locals} = sequence_args, meta: sequence_meta} =
|
||||
sequence_ast,
|
||||
iterable_ast,
|
||||
%{
|
||||
args: %{locals: %{body: _} = locals} = sequence_args,
|
||||
meta: sequence_meta
|
||||
} = sequence_ast,
|
||||
env
|
||||
) do
|
||||
sequence_name = sequence_meta[:sequence_name] || sequence_args[:sequence_name]
|
||||
|
||||
# remove the iterable from the parameter applications,
|
||||
# since it will be injected after this.
|
||||
_params =
|
||||
Enum.reduce(params, [], fn
|
||||
# Remove point_group from parameter appls
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: :point_group}}}, acc -> acc
|
||||
# Remove every_point from parameter appls
|
||||
%{kind: :parameter_application, args: %{data_value: %{kind: :every_point}}}, acc -> acc
|
||||
# Everything else gets added back
|
||||
ast, acc -> acc ++ [ast]
|
||||
end)
|
||||
sequence_name =
|
||||
sequence_meta[:sequence_name] || sequence_args[:sequence_name]
|
||||
|
||||
# will be a point_group or every_point node
|
||||
group_ast = loop_parameter_appl_ast.args.data_value
|
||||
group_ast = iterable_ast.args.data_value
|
||||
# check if it's a point_group first, then fall back to every_point
|
||||
point_group_arg =
|
||||
group_ast.args[:point_group_id] || group_ast.args[:resource_id] ||
|
||||
group_ast.args[:every_point_type]
|
||||
|
||||
# lookup all point_groups related to this value
|
||||
case FarmbotCeleryScript.SysCalls.get_point_group(point_group_arg) do
|
||||
case FarmbotCeleryScript.SysCalls.find_points_via_group(point_group_arg) do
|
||||
{:error, reason} ->
|
||||
quote location: :keep, do: Macro.escape({:error, unquote(reason)})
|
||||
|
||||
%{name: group_name} = point_group ->
|
||||
total = Enum.count(point_group.point_ids)
|
||||
# Map over all the points returned by `get_point_group/1`
|
||||
# Map over all the points returned by `find_points_via_group/1`
|
||||
{body, _} =
|
||||
Enum.reduce(point_group.point_ids, {[], 1}, fn point_id, {acc, index} ->
|
||||
Enum.reduce(point_group.point_ids, {[], 1}, fn point_id,
|
||||
{acc, index} ->
|
||||
# check if it's an every_point node first, if not fall back go generic pointer
|
||||
pointer_type = group_ast.args[:every_point_type] || "GenericPointer"
|
||||
|
||||
|
@ -67,7 +63,7 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
kind: :parameter_application,
|
||||
args: %{
|
||||
# inject the replacement with the same label
|
||||
label: loop_parameter_appl_ast.args.label,
|
||||
label: iterable_ast.args.label,
|
||||
data_value: %FarmbotCeleryScript.AST{
|
||||
kind: :point,
|
||||
args: %{pointer_type: pointer_type, pointer_id: point_id}
|
||||
|
@ -83,7 +79,10 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
|
||||
%{name: name, x: x, y: y, z: z} ->
|
||||
pos = FarmbotCeleryScript.FormatUtil.format_coord(x, y, z)
|
||||
"unnamed iterable sequence [#{index} / #{total}] - #{name} #{pos}"
|
||||
|
||||
"unnamed iterable sequence [#{index} / #{total}] - #{name} #{
|
||||
pos
|
||||
}"
|
||||
|
||||
_ ->
|
||||
"unknown iterable [#{index} / #{total}]"
|
||||
|
@ -91,7 +90,7 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
|
||||
# compile a `sequence` ast, injecting the appropriate `point` ast with
|
||||
# the matching `label`
|
||||
# TODO(Connor) - the body of this ast should have the
|
||||
# TODO(Connor) - the body of this ast should have the
|
||||
# params as sorted earlier. Figure out why this doesn't work
|
||||
body =
|
||||
compile_sequence(
|
||||
|
@ -113,11 +112,48 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
end
|
||||
end
|
||||
|
||||
def compile_sequence(%{args: %{locals: %{body: params}} = args, body: block, meta: meta}, env) do
|
||||
def create_better_params(body, env) do
|
||||
parameter_declarations =
|
||||
Enum.reduce(env, %{}, fn
|
||||
{key, value}, map ->
|
||||
encoded_label = "#{key}"
|
||||
|
||||
if String.starts_with?(encoded_label, "unsafe_") do
|
||||
Map.put(map, IdentifierSanitizer.to_string(encoded_label), value)
|
||||
else
|
||||
map
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.reduce(body, parameter_declarations, fn ast, map ->
|
||||
case ast do
|
||||
%{kind: :parameter_application} ->
|
||||
args = Map.fetch!(ast, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
Map.put(map, label, Map.fetch!(args, :data_value))
|
||||
|
||||
%{kind: :variable_declaration} ->
|
||||
args = Map.fetch!(ast, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
Map.put(map, label, Map.fetch!(args, :data_value))
|
||||
|
||||
%{kind: :parameter_declaration} ->
|
||||
map
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def compile_sequence(
|
||||
%{args: %{locals: %{body: params}} = args, body: block, meta: meta},
|
||||
env
|
||||
) do
|
||||
# Sort the args.body into two arrays.
|
||||
# The `params` side gets turned into
|
||||
# a keyword list. These `params` are passed in from a previous sequence.
|
||||
# The `body` side declares variables in _this_ scope.
|
||||
# === DON'T USE THIS IN NEW CODE.
|
||||
# SCHEDULED FOR DEPRECATION.
|
||||
# USE `better_params` INSTEAD.
|
||||
{params_fetch, body} =
|
||||
Enum.reduce(params, {[], []}, fn ast, {params, body} = _acc ->
|
||||
case ast do
|
||||
|
@ -141,6 +177,8 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
|
||||
steps = add_sequence_init_and_complete_logs(steps, sequence_name)
|
||||
|
||||
better_params = create_better_params(params, env)
|
||||
|
||||
[
|
||||
quote location: :keep do
|
||||
fn params ->
|
||||
|
@ -151,7 +189,7 @@ defmodule FarmbotCeleryScript.Compiler.Sequence do
|
|||
# parent = Keyword.fetch!(params, :parent)
|
||||
unquote_splicing(params_fetch)
|
||||
unquote_splicing(assignments)
|
||||
|
||||
better_params = unquote(better_params)
|
||||
# Unquote the remaining sequence steps.
|
||||
unquote(steps)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
defmodule FarmbotCeleryScript.Compiler.UpdateResource do
|
||||
alias FarmbotCeleryScript.{AST, DotProps}
|
||||
|
||||
def update_resource(%AST{args: args, body: body}, _env) do
|
||||
quote location: :keep do
|
||||
me = unquote(__MODULE__)
|
||||
variable = unquote(Map.fetch!(args, :resource))
|
||||
update = unquote(unpair(body, %{}))
|
||||
|
||||
# Go easy on the API...
|
||||
case variable do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args, update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args, update)
|
||||
|
||||
res ->
|
||||
raise "Resource error. Please notfiy support: #{inspect(res)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def do_update(%{pointer_id: id, pointer_type: kind}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(kind, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(%{resource_id: id, resource_type: kind}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(kind, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(%{args: %{pointer_id: id, pointer_type: k}}, update_params) do
|
||||
FarmbotCeleryScript.SysCalls.update_resource(k, id, update_params)
|
||||
end
|
||||
|
||||
def do_update(other, update) do
|
||||
raise String.trim("""
|
||||
MARK AS can only be used to mark resources like plants and devices.
|
||||
It cannot be used on things like coordinates.
|
||||
Ensure that your sequences and farm events us MARK AS on plants and not
|
||||
coordinates (#{inspect(other)} / #{inspect(update)})
|
||||
""")
|
||||
end
|
||||
|
||||
defp unpair([pair | rest], acc) do
|
||||
key = Map.fetch!(pair.args, :label)
|
||||
val = Map.fetch!(pair.args, :value)
|
||||
next_acc = Map.merge(acc, DotProps.create(key, val))
|
||||
unpair(rest, next_acc)
|
||||
end
|
||||
|
||||
defp unpair([], acc) do
|
||||
acc
|
||||
end
|
||||
end
|
|
@ -2,7 +2,10 @@ defmodule FarmbotCeleryScript.Compiler.VariableDeclaration do
|
|||
alias FarmbotCeleryScript.{Compiler, Compiler.IdentifierSanitizer}
|
||||
|
||||
@doc "Compiles a variable asignment"
|
||||
def variable_declaration(%{args: %{label: var_name, data_value: data_value_ast}}, env) do
|
||||
def variable_declaration(
|
||||
%{args: %{label: var_name, data_value: data_value_ast}},
|
||||
env
|
||||
) do
|
||||
# Compiles the `data_value`
|
||||
# and assigns the result to a variable named `label`
|
||||
# Example:
|
||||
|
@ -26,7 +29,8 @@ defmodule FarmbotCeleryScript.Compiler.VariableDeclaration do
|
|||
var_name = IdentifierSanitizer.to_variable(var_name)
|
||||
|
||||
quote location: :keep do
|
||||
unquote({var_name, [], nil}) = unquote(Compiler.compile_ast(data_value_ast, env))
|
||||
unquote({var_name, [], nil}) =
|
||||
unquote(Compiler.compile_ast(data_value_ast, env))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,14 +18,18 @@ defmodule FarmbotCeleryScript.Corpus do
|
|||
@corpus_tag tag
|
||||
|
||||
# Load and decode each arg in the json into an Arg struct
|
||||
@args Enum.map(args, fn %{"name" => name, "allowed_values" => allowed_values} = a ->
|
||||
@args Enum.map(args, fn %{"name" => name, "allowed_values" => allowed_values} =
|
||||
a ->
|
||||
%Arg{name: name, allowed_values: allowed_values, doc: a["doc"]}
|
||||
end)
|
||||
|
||||
# Load and decode each node in the json into a Node struct.
|
||||
# This also expands the `allowed_args` into their respective Arg relationship.
|
||||
@nodes Enum.map(@nodes, fn %{"name" => name, "allowed_args" => aa, "allowed_body_types" => abt} =
|
||||
n ->
|
||||
@nodes Enum.map(@nodes, fn %{
|
||||
"name" => name,
|
||||
"allowed_args" => aa,
|
||||
"allowed_body_types" => abt
|
||||
} = n ->
|
||||
allowed_args =
|
||||
Enum.map(aa, fn arg_name ->
|
||||
Enum.find(@args, fn
|
||||
|
@ -34,7 +38,12 @@ defmodule FarmbotCeleryScript.Corpus do
|
|||
end) || Mix.raise("Unknown CeleryScript argument: #{arg_name}")
|
||||
end)
|
||||
|
||||
%Node{name: name, allowed_args: allowed_args, allowed_body_types: abt, doc: n["doc"]}
|
||||
%Node{
|
||||
name: name,
|
||||
allowed_args: allowed_args,
|
||||
allowed_body_types: abt,
|
||||
doc: n["doc"]
|
||||
}
|
||||
end)
|
||||
|
||||
# Struct should never be created manually.
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
defmodule FarmbotCeleryScript.DotProps do
|
||||
@dot "."
|
||||
@doc ~S"""
|
||||
Takes a "dotted" key and val.
|
||||
Returns deeply nested hash.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> create("foo.bar.baz", 321)
|
||||
%{"foo" => %{"bar" => %{"baz" => 321}}}
|
||||
|
||||
iex> create("foo", "bar")
|
||||
%{"foo" => "bar"}
|
||||
"""
|
||||
def create(dotted, val) do
|
||||
[key | list] = dotted |> String.split(@dot) |> Enum.reverse()
|
||||
|
||||
Enum.reduce(list, %{key => val}, fn next_key, acc ->
|
||||
%{next_key => acc}
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,6 @@
|
|||
defmodule FarmbotCeleryScript.FormatUtil do
|
||||
def format_float(nil), do: nil
|
||||
|
||||
def format_float(value) when is_integer(value) do
|
||||
format_float(value / 1)
|
||||
end
|
||||
|
|
|
@ -67,7 +67,12 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
Calls are executed in a first in first out buffer, with things being added
|
||||
by `execute/2` taking priority.
|
||||
"""
|
||||
@spec schedule(GenServer.server(), AST.t() | [Compiler.compiled()], DateTime.t(), map()) ::
|
||||
@spec schedule(
|
||||
GenServer.server(),
|
||||
AST.t() | [Compiler.compiled()],
|
||||
DateTime.t(),
|
||||
map()
|
||||
) ::
|
||||
{:ok, reference()}
|
||||
def schedule(scheduler_pid \\ __MODULE__, celery_script, at, data)
|
||||
|
||||
|
@ -147,12 +152,12 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
case DateTime.diff(DateTime.utc_now(), at, :millisecond) do
|
||||
# now is before the next date
|
||||
diff_ms when diff_ms < 0 ->
|
||||
from_now =
|
||||
DateTime.utc_now()
|
||||
|> DateTime.add(abs(diff_ms), :millisecond)
|
||||
|> Timex.from_now()
|
||||
|
||||
Logger.info("Next execution is still #{diff_ms}ms too early (#{from_now})")
|
||||
# from_now =
|
||||
# DateTime.utc_now()
|
||||
# |> DateTime.add(abs(diff_ms), :millisecond)
|
||||
# |> Timex.from_now()
|
||||
# msg = "Next execution is still #{diff_ms}ms too early (#{from_now})"
|
||||
# Logger.info(msg)
|
||||
|
||||
state
|
||||
|> schedule_next_checkup(abs(diff_ms))
|
||||
|
@ -160,8 +165,8 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
|
||||
# now is more than the grace period past schedule time
|
||||
diff_ms when diff_ms > @grace_period_ms ->
|
||||
from_now = Timex.from_now(at)
|
||||
Logger.info("Next execution is #{diff_ms}ms too late (#{from_now})")
|
||||
# from_now = Timex.from_now(at)
|
||||
# Logger.info("Next execution is #{diff_ms}ms too late (#{from_now})")
|
||||
|
||||
state
|
||||
|> pop_next()
|
||||
|
@ -171,7 +176,9 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
|
||||
# now is late, but less than the grace period late
|
||||
diff_ms when diff_ms >= 0 when diff_ms <= @grace_period_ms ->
|
||||
Logger.info("Next execution is ready for execution: #{Timex.from_now(at)}")
|
||||
Logger.info(
|
||||
"Next execution is ready for execution: #{Timex.from_now(at)}"
|
||||
)
|
||||
|
||||
state
|
||||
|> execute_next()
|
||||
|
@ -179,8 +186,15 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, {scheduled_at, executed_at, pid}, result}, state) do
|
||||
send(pid, {FarmbotCeleryScript, {:scheduled_execution, scheduled_at, executed_at, result}})
|
||||
def handle_info(
|
||||
{:step_complete, {scheduled_at, executed_at, pid}, result},
|
||||
state
|
||||
) do
|
||||
send(
|
||||
pid,
|
||||
{FarmbotCeleryScript,
|
||||
{:scheduled_execution, scheduled_at, executed_at, result}}
|
||||
)
|
||||
|
||||
state
|
||||
|> pop_next()
|
||||
|
@ -194,7 +208,9 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
scheduler_pid = self()
|
||||
|
||||
scheduled_pid =
|
||||
spawn(fn -> StepRunner.step(scheduler_pid, {at, DateTime.utc_now(), pid}, compiled) end)
|
||||
spawn(fn ->
|
||||
StepRunner.step(scheduler_pid, {at, DateTime.utc_now(), pid}, compiled)
|
||||
end)
|
||||
|
||||
%{state | scheduled_pid: scheduled_pid}
|
||||
end
|
||||
|
@ -291,7 +307,8 @@ defmodule FarmbotCeleryScript.Scheduler do
|
|||
%{state | monitors: monitors}
|
||||
end
|
||||
|
||||
@spec add(state(), compiled_ast(), DateTime.t(), data :: map(), pid()) :: state()
|
||||
@spec add(state(), compiled_ast(), DateTime.t(), data :: map(), pid()) ::
|
||||
state()
|
||||
defp add(state, compiled, at, data, pid) do
|
||||
%{state | compiled: [{compiled, at, data, pid} | state.compiled]}
|
||||
|> index_next()
|
||||
|
|
|
@ -15,18 +15,20 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
@type error :: {:error, String.t()}
|
||||
@type ok_or_error :: :ok | error
|
||||
|
||||
# "x", "y", or "z"
|
||||
@type axis :: String.t()
|
||||
@type package :: String.t()
|
||||
@type resource_id :: integer()
|
||||
|
||||
@callback calibrate(axis) :: ok_or_error
|
||||
@callback change_ownership(email :: String.t(), secret :: binary(), server :: String.t()) ::
|
||||
@callback change_ownership(
|
||||
email :: String.t(),
|
||||
secret :: binary(),
|
||||
server :: String.t()
|
||||
) ::
|
||||
ok_or_error
|
||||
@callback check_update() :: ok_or_error
|
||||
@callback coordinate(x :: number, y :: number, z :: number) ::
|
||||
%{x: number(), y: number(), z: number()} | error
|
||||
@callback dump_info() :: ok_or_error
|
||||
@callback emergency_lock() :: ok_or_error
|
||||
@callback emergency_unlock() :: ok_or_error
|
||||
@callback execute_script(package, args :: map()) :: ok_or_error
|
||||
|
@ -48,26 +50,37 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
%{x: number(), y: number(), z: number()} | error()
|
||||
@callback home(axis, speed :: number()) :: ok_or_error
|
||||
@callback install_first_party_farmware() :: ok_or_error
|
||||
@callback move_absolute(x :: number(), y :: number(), z :: number(), speed :: number()) ::
|
||||
@callback move_absolute(
|
||||
x :: number(),
|
||||
y :: number(),
|
||||
z :: number(),
|
||||
speed :: number()
|
||||
) ::
|
||||
ok_or_error
|
||||
# ?
|
||||
@callback named_pin(named_pin_type :: String.t(), resource_id) :: map() | integer | error()
|
||||
@callback named_pin(named_pin_type :: String.t(), resource_id) ::
|
||||
map() | integer | error()
|
||||
@callback nothing() :: any()
|
||||
@callback point(point_type :: String.t(), resource_id) :: number() | error()
|
||||
@callback power_off() :: ok_or_error
|
||||
@callback read_pin(pin_num :: number(), pin_mode :: number()) :: number | error()
|
||||
@callback read_pin(pin_num :: number(), pin_mode :: number()) ::
|
||||
number | error()
|
||||
@callback read_cached_pin(pin_num :: number()) :: number | error()
|
||||
@callback toggle_pin(pin_num :: number()) :: ok_or_error
|
||||
@callback read_status() :: ok_or_error
|
||||
@callback reboot() :: ok_or_error
|
||||
@callback resource_update(String.t(), resource_id, map()) :: ok_or_error
|
||||
@callback send_message(type :: String.t(), message :: String.t(), [atom]) :: ok_or_error
|
||||
@callback send_message(type :: String.t(), message :: String.t(), [atom]) ::
|
||||
ok_or_error
|
||||
@callback set_servo_angle(pin :: number(), value :: number()) :: ok_or_error
|
||||
@callback set_pin_io_mode(pin :: number(), mode :: number()) :: ok_or_error
|
||||
@callback set_user_env(env_name :: String.t(), env_value :: String.t()) :: ok_or_error
|
||||
@callback set_user_env(env_name :: String.t(), env_value :: String.t()) ::
|
||||
ok_or_error
|
||||
@callback sync() :: ok_or_error
|
||||
@callback wait(millis :: number()) :: ok_or_error
|
||||
@callback write_pin(pin_num :: number(), pin_mode :: number(), pin_value :: number) ::
|
||||
@callback write_pin(
|
||||
pin_num :: number(),
|
||||
pin_mode :: number(),
|
||||
pin_value :: number
|
||||
) ::
|
||||
ok_or_error
|
||||
@callback zero(axis) :: ok_or_error
|
||||
|
||||
|
@ -77,10 +90,14 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
@callback eval_assertion(comment :: String.t(), expression :: String.t()) ::
|
||||
true | false | error()
|
||||
|
||||
@callback get_point_group(String.t() | resource_id) :: %{required(:point_ids) => [resource_id]}
|
||||
@callback find_points_via_group(String.t() | resource_id) :: %{
|
||||
required(:point_ids) => [resource_id]
|
||||
}
|
||||
@callback update_resource(kind :: String.t(), resource_id, params :: map()) ::
|
||||
ok_or_error
|
||||
|
||||
def get_point_group(sys_calls \\ @sys_calls, point_group_id) do
|
||||
point_group_or_error(sys_calls, :get_point_group, [point_group_id])
|
||||
def find_points_via_group(sys_calls \\ @sys_calls, point_group_id) do
|
||||
point_group_or_error(sys_calls, :find_points_via_group, [point_group_id])
|
||||
end
|
||||
|
||||
def format_lhs(sys_calls \\ @sys_calls, lhs)
|
||||
|
@ -90,14 +107,18 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
def format_lhs(_sys_calls, "z"), do: "current z position"
|
||||
def format_lhs(_sys_calls, "pin" <> num), do: "Pin #{num} value"
|
||||
|
||||
def format_lhs(sys_calls, %{kind: :named_pin, args: %{pin_type: type, pin_id: pin_id}}) do
|
||||
def format_lhs(sys_calls, %{
|
||||
kind: :named_pin,
|
||||
args: %{pin_type: type, pin_id: pin_id}
|
||||
}) do
|
||||
case named_pin(sys_calls, type, pin_id) do
|
||||
%{label: label} -> label
|
||||
{:error, _reason} -> "unknown left hand side"
|
||||
end
|
||||
end
|
||||
|
||||
def eval_assertion(sys_calls \\ @sys_calls, comment, expression) when is_binary(expression) do
|
||||
def eval_assertion(sys_calls \\ @sys_calls, comment, expression)
|
||||
when is_binary(expression) do
|
||||
case sys_calls.eval_assertion(comment, expression) do
|
||||
true ->
|
||||
true
|
||||
|
@ -121,11 +142,13 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
apply(@sys_calls, :log, [message, force?])
|
||||
end
|
||||
|
||||
def sequence_init_log(sys_calls \\ @sys_calls, message) when is_binary(message) do
|
||||
def sequence_init_log(sys_calls \\ @sys_calls, message)
|
||||
when is_binary(message) do
|
||||
apply(sys_calls, :sequence_init_log, [message])
|
||||
end
|
||||
|
||||
def sequence_complete_log(sys_calls \\ @sys_calls, message) when is_binary(message) do
|
||||
def sequence_complete_log(sys_calls \\ @sys_calls, message)
|
||||
when is_binary(message) do
|
||||
apply(sys_calls, :sequence_complete_log, [message])
|
||||
end
|
||||
|
||||
|
@ -148,10 +171,6 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
coord_or_error(sys_calls, :coordinate, [x, y, z])
|
||||
end
|
||||
|
||||
def dump_info(sys_calls \\ @sys_calls) do
|
||||
ok_or_error(sys_calls, :dump_info, [])
|
||||
end
|
||||
|
||||
def emergency_lock(sys_calls \\ @sys_calls) do
|
||||
ok_or_error(sys_calls, :emergency_lock, [])
|
||||
end
|
||||
|
@ -160,11 +179,13 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
ok_or_error(sys_calls, :emergency_unlock, [])
|
||||
end
|
||||
|
||||
def execute_script(sys_calls \\ @sys_calls, package, %{} = env) when is_binary(package) do
|
||||
def execute_script(sys_calls \\ @sys_calls, package, %{} = env)
|
||||
when is_binary(package) do
|
||||
ok_or_error(sys_calls, :execute_script, [package, env])
|
||||
end
|
||||
|
||||
def update_farmware(sys_calls \\ @sys_calls, package) when is_binary(package) do
|
||||
def update_farmware(sys_calls \\ @sys_calls, package)
|
||||
when is_binary(package) do
|
||||
ok_or_error(sys_calls, :update_farmware, [package])
|
||||
end
|
||||
|
||||
|
@ -276,10 +297,6 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
ok_or_error(sys_calls, :reboot, [])
|
||||
end
|
||||
|
||||
def resource_update(sys_calls \\ @sys_calls, kind, id, params) do
|
||||
ok_or_error(sys_calls, :resource_update, [kind, id, params])
|
||||
end
|
||||
|
||||
def send_message(sys_calls \\ @sys_calls, kind, msg, channels) do
|
||||
ok_or_error(sys_calls, :send_message, [kind, msg, channels])
|
||||
end
|
||||
|
@ -312,6 +329,10 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
ok_or_error(sys_calls, :zero, [axis])
|
||||
end
|
||||
|
||||
def update_resource(sys_calls \\ @sys_calls, kind, id, params) do
|
||||
ok_or_error(sys_calls, :update_resource, [kind, id, params])
|
||||
end
|
||||
|
||||
defp ok_or_error(sys_calls, fun, args) do
|
||||
case apply(sys_calls, fun, args) do
|
||||
:ok -> :ok
|
||||
|
@ -336,8 +357,14 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
|
||||
defp coord_or_error(sys_calls, fun, args) do
|
||||
case apply(sys_calls, fun, args) do
|
||||
%{x: x, y: y, z: z} = coord when is_number(x) when is_number(y) when is_number(z) -> coord
|
||||
error -> or_error(sys_calls, fun, args, error)
|
||||
%{x: x, y: y, z: z} = coord
|
||||
when is_number(x)
|
||||
when is_number(y)
|
||||
when is_number(z) ->
|
||||
coord
|
||||
|
||||
error ->
|
||||
or_error(sys_calls, fun, args, error)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -348,7 +375,8 @@ defmodule FarmbotCeleryScript.SysCalls do
|
|||
end
|
||||
end
|
||||
|
||||
defp or_error(_sys_calls, _fun, _args, {:error, reason}) when is_binary(reason) do
|
||||
defp or_error(_sys_calls, _fun, _args, {:error, reason})
|
||||
when is_binary(reason) do
|
||||
{:error, reason}
|
||||
end
|
||||
|
||||
|
|
|
@ -28,9 +28,6 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
@impl true
|
||||
def coordinate(x, y, z), do: error(:coordinate, [x, y, z])
|
||||
|
||||
@impl true
|
||||
def dump_info(), do: error(:dump_info, [])
|
||||
|
||||
@impl true
|
||||
def emergency_lock(), do: error(:emergency_lock, [])
|
||||
|
||||
|
@ -77,28 +74,32 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
def get_sequence(resource_id), do: error(:get_sequence, [resource_id])
|
||||
|
||||
@impl true
|
||||
def get_toolslot_for_tool(resource_id), do: error(:get_toolslot_for_tool, [resource_id])
|
||||
def get_toolslot_for_tool(resource_id),
|
||||
do: error(:get_toolslot_for_tool, [resource_id])
|
||||
|
||||
@impl true
|
||||
def home(axis, speed), do: error(:home, [axis, speed])
|
||||
|
||||
@impl true
|
||||
def install_first_party_farmware(), do: error(:install_first_party_farmware, [])
|
||||
def install_first_party_farmware(),
|
||||
do: error(:install_first_party_farmware, [])
|
||||
|
||||
@impl true
|
||||
def move_absolute(x, y, z, speed), do: error(:move_absolute, [x, y, z, speed])
|
||||
|
||||
@impl true
|
||||
def named_pin(named_pin_type, resource_id), do: error(:named_pin, [named_pin_type, resource_id])
|
||||
def named_pin(named_pin_type, resource_id),
|
||||
do: error(:named_pin, [named_pin_type, resource_id])
|
||||
|
||||
@impl true
|
||||
def nothing(), do: error(:nothing, [])
|
||||
|
||||
@impl true
|
||||
def point(point_type, resource_id), do: error(:point, [point_type, resource_id])
|
||||
def point(point_type, resource_id),
|
||||
do: error(:point, [point_type, resource_id])
|
||||
|
||||
@impl true
|
||||
def get_point_group(id_or_type), do: error(:get_point_group, [id_or_type])
|
||||
def find_points_via_group(id), do: error(:find_points_via_group, [id])
|
||||
|
||||
@impl true
|
||||
def power_off(), do: error(:power_off, [])
|
||||
|
@ -119,11 +120,8 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
def reboot(), do: error(:reboot, [])
|
||||
|
||||
@impl true
|
||||
def resource_update(kind, resource_id, data),
|
||||
do: error(:resource_update, [kind, resource_id, data])
|
||||
|
||||
@impl true
|
||||
def send_message(type, message, channels), do: error(:send_message, [type, message, channels])
|
||||
def send_message(type, message, channels),
|
||||
do: error(:send_message, [type, message, channels])
|
||||
|
||||
@impl true
|
||||
def set_servo_angle(pin, value), do: error(:set_servo_angle, [pin, value])
|
||||
|
@ -132,7 +130,8 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
def set_pin_io_mode(pin, mode), do: error(:set_pin_io_mode, [pin, mode])
|
||||
|
||||
@impl true
|
||||
def set_user_env(env_name, env_value), do: error(:set_user_env, [env_name, env_value])
|
||||
def set_user_env(env_name, env_value),
|
||||
do: error(:set_user_env, [env_name, env_value])
|
||||
|
||||
@impl true
|
||||
def sync(), do: error(:sync, [])
|
||||
|
@ -144,11 +143,16 @@ defmodule FarmbotCeleryScript.SysCalls.Stubs do
|
|||
def write_pin(pin_num, pin_mode, pin_value),
|
||||
do: error(:write_pin, [pin_num, pin_mode, pin_value])
|
||||
|
||||
@impl true
|
||||
def update_resource(kind, id, params),
|
||||
do: error(:update_resource, [kind, id, params])
|
||||
|
||||
@impl true
|
||||
def zero(axis), do: error(:zero, [axis])
|
||||
|
||||
@impl true
|
||||
def eval_assertion(comment, expression), do: error(:eval_assertion, [comment, expression])
|
||||
def eval_assertion(comment, expression),
|
||||
do: error(:eval_assertion, [comment, expression])
|
||||
|
||||
defp error(fun, _args) do
|
||||
msg = """
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
defmodule FarmbotCeleryScript.MixProject do
|
||||
use Mix.Project
|
||||
@version Path.join([__DIR__, "..", "VERSION"]) |> File.read!() |> String.trim()
|
||||
@elixir_version Path.join([__DIR__, "..", "ELIXIR_VERSION"]) |> File.read!() |> String.trim()
|
||||
|
||||
@version Path.join([__DIR__, "..", "VERSION"])
|
||||
|> File.read!()
|
||||
|> String.trim()
|
||||
@elixir_version Path.join([__DIR__, "..", "ELIXIR_VERSION"])
|
||||
|> File.read!()
|
||||
|> String.trim()
|
||||
|
||||
def project do
|
||||
[
|
||||
|
@ -39,7 +44,11 @@ defmodule FarmbotCeleryScript.MixProject do
|
|||
end
|
||||
|
||||
def elixirc_paths(:test),
|
||||
do: ["lib", Path.expand("./test/support"), Path.expand("../test/support/celery_script")]
|
||||
do: [
|
||||
"lib",
|
||||
Path.expand("./test/support"),
|
||||
Path.expand("../test/support/celery_script")
|
||||
]
|
||||
|
||||
def elixirc_paths(_), do: ["lib"]
|
||||
|
||||
|
@ -57,7 +66,9 @@ defmodule FarmbotCeleryScript.MixProject do
|
|||
{:jason, "~> 1.1"},
|
||||
{:timex, "~> 3.4"},
|
||||
{:excoveralls, "~> 0.10", only: [:test], targets: [:host]},
|
||||
{:dialyxir, "~> 1.0.0-rc.3", only: [:dev], targets: [:host], runtime: false},
|
||||
{:mimic, "~> 1.1", only: :test},
|
||||
{:dialyxir, "~> 1.0.0-rc.3",
|
||||
only: [:dev], targets: [:host], runtime: false},
|
||||
{:ex_doc, "~> 0.21.2", only: [:dev], targets: [:host], runtime: false}
|
||||
]
|
||||
end
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
defmodule FarmbotCeleryScript.DotPropsTest do
|
||||
use ExUnit.Case
|
||||
doctest FarmbotCeleryScript.DotProps, import: true
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
defmodule FarmbotCeleryScript.AST.FactoryTest do
|
||||
use ExUnit.Case, async: true
|
||||
doctest FarmbotCeleryScript.AST.Factory, import: true
|
||||
end
|
|
@ -0,0 +1,122 @@
|
|||
defmodule FarmbotCeleryScript.CompilerGroupsTest do
|
||||
use ExUnit.Case, async: true
|
||||
use Mimic
|
||||
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias FarmbotCeleryScript.SysCalls.Stubs
|
||||
alias FarmbotCeleryScript.Compiler.Sequence
|
||||
setup :verify_on_exit!
|
||||
|
||||
test "compilation of point_group in parameter application" do
|
||||
fake_point_ids = [4, 5, 6, 7]
|
||||
|
||||
expect(Stubs, :find_points_via_group, fn _ ->
|
||||
%{name: "woosh", point_ids: fake_point_ids}
|
||||
end)
|
||||
|
||||
expect(Stubs, :point, 4, fn _kind, id ->
|
||||
# EDGE CASE: Handle malformed stuff by ensuring that 50% of
|
||||
# points have no name.
|
||||
|
||||
if rem(id, 2) == 0 do
|
||||
%{name: "from the test suite %%", x: 6, y: 7, z: 8}
|
||||
else
|
||||
%{x: 6, y: 7, z: 8}
|
||||
end
|
||||
end)
|
||||
|
||||
result = Sequence.sequence(fake_ast(), [])
|
||||
|
||||
length1 = 2 + length(fake_point_ids)
|
||||
length2 = length(result)
|
||||
assert length2 === length1
|
||||
# ============================================
|
||||
# ABOUT THIS (brittle) TEST:
|
||||
# You should not write tests like this and
|
||||
# there is a high liklihood that the code below
|
||||
# will break in the future.
|
||||
# This is especially true if you intend to change
|
||||
# the behavior of Sequence.sequence/2.
|
||||
# If you WERE NOT EXPECTING to change the behavior
|
||||
# of Sequence.sequence/2 and this test fails,
|
||||
# you should consider it a true failure that
|
||||
# requires investigation.
|
||||
# IT IS OK TO REPLACE THIS TEST WITH BETTER
|
||||
# TESTS.
|
||||
# ============================================
|
||||
canary_actual = :crypto.hash(:sha, Macro.to_string(result))
|
||||
|
||||
canary_expected =
|
||||
<<136, 140, 48, 226, 216, 155, 178, 103, 244, 88, 225, 146, 130, 216, 125,
|
||||
72, 113, 195, 65, 1>>
|
||||
|
||||
# READ THE NOTE ABOVE IF THIS TEST FAILS!!!
|
||||
assert canary_expected == canary_actual
|
||||
end
|
||||
|
||||
defp fake_ast() do
|
||||
%AST{
|
||||
args: %{
|
||||
locals: %AST{
|
||||
args: %{},
|
||||
body: [
|
||||
%AST{
|
||||
args: %{
|
||||
default_value: %AST{
|
||||
args: %{pointer_id: 1670, pointer_type: "Plant"},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :point,
|
||||
meta: nil
|
||||
},
|
||||
label: "parent"
|
||||
},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :parameter_declaration,
|
||||
meta: nil
|
||||
},
|
||||
%AST{
|
||||
args: %{
|
||||
data_value: %AST{
|
||||
args: %{point_group_id: 34},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :point_group,
|
||||
meta: nil
|
||||
},
|
||||
label: "parent"
|
||||
},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :parameter_application,
|
||||
meta: nil
|
||||
}
|
||||
],
|
||||
comment: nil,
|
||||
kind: :scope_declaration,
|
||||
meta: nil
|
||||
},
|
||||
sequence_name: "Pogo",
|
||||
version: 20_180_209
|
||||
},
|
||||
body: [
|
||||
%AST{
|
||||
kind: :move_absolute,
|
||||
args: %{
|
||||
speed: 100,
|
||||
location: %AST{kind: :identifier, args: %{label: "parent"}},
|
||||
offset: %AST{
|
||||
kind: :coordinate,
|
||||
args: %{x: -20, y: -20, z: -20}
|
||||
}
|
||||
},
|
||||
body: []
|
||||
}
|
||||
],
|
||||
comment: nil,
|
||||
kind: :sequence,
|
||||
meta: %{sequence_name: "Pogo"}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -34,7 +34,10 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
# The compiler expects the `env` argument to be already sanatized.
|
||||
# When supplying the env for this test, we need to make sure the
|
||||
# `provided_by_caller` variable name is sanatized
|
||||
sanatized_env = [{IdentifierSanitizer.to_variable("provided_by_caller"), 900}]
|
||||
sanatized_env = [
|
||||
{IdentifierSanitizer.to_variable("provided_by_caller"), 900}
|
||||
]
|
||||
|
||||
[body_item] = Compiler.compile(sequence, sanatized_env)
|
||||
assert body_item.() == 900
|
||||
|
||||
|
@ -48,7 +51,9 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
}
|
||||
]
|
||||
|
||||
compiled_celery_env = Compiler.Utils.compile_params_to_function_args(celery_env, [])
|
||||
compiled_celery_env =
|
||||
Compiler.Utils.compile_params_to_function_args(celery_env, [])
|
||||
|
||||
[body_item] = Compiler.compile(sequence, compiled_celery_env)
|
||||
assert body_item.() == 600
|
||||
end
|
||||
|
@ -74,12 +79,15 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
end
|
||||
|
||||
test "identifier sanitization" do
|
||||
label = "System.cmd(\"rm\", [\"-rf /*\"])"
|
||||
label = "System.cmd(\"echo\", [\"lol\"])"
|
||||
value_ast = AST.Factory.new("coordinate", x: 1, y: 1, z: 1)
|
||||
identifier_ast = AST.Factory.new("identifier", label: label)
|
||||
|
||||
parameter_application_ast =
|
||||
AST.Factory.new("parameter_application", label: label, data_value: value_ast)
|
||||
AST.Factory.new("parameter_application",
|
||||
label: label,
|
||||
data_value: value_ast
|
||||
)
|
||||
|
||||
celery_ast = %AST{
|
||||
kind: :sequence,
|
||||
|
@ -112,11 +120,19 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
unsafe_U3lzdGVtLmNtZCgiZWNobyIsIFsibG9sIl0p = FarmbotCeleryScript.SysCalls.coordinate(1, 1, 1)
|
||||
|
||||
#{var_name} =
|
||||
FarmbotCeleryScript.SysCalls.coordinate(1, 1, 1)
|
||||
better_params = %{
|
||||
"System.cmd(\\"echo\\", [\\"lol\\"])" => %FarmbotCeleryScript.AST{
|
||||
args: %{x: 1, y: 1, z: 1},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :coordinate,
|
||||
meta: nil
|
||||
}
|
||||
}
|
||||
|
||||
[fn -> #{var_name} end]
|
||||
[fn -> unsafe_U3lzdGVtLmNtZCgiZWNobyIsIFsibG9sIl0p end]
|
||||
end
|
||||
]
|
||||
""")
|
||||
|
@ -347,6 +363,114 @@ defmodule FarmbotCeleryScript.CompilerTest do
|
|||
""")
|
||||
end
|
||||
|
||||
test "`update_resource`: " do
|
||||
compiled =
|
||||
"test/fixtures/mark_variable_removed.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|> AST.decode()
|
||||
|> compile()
|
||||
|
||||
assert compiled ==
|
||||
strip_nl("""
|
||||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
|
||||
unsafe_cGFyZW50 =
|
||||
Keyword.get(params, :unsafe_cGFyZW50, FarmbotCeleryScript.SysCalls.coordinate(1, 2, 3))
|
||||
|
||||
better_params = %{}
|
||||
|
||||
[
|
||||
fn ->
|
||||
me = FarmbotCeleryScript.Compiler.UpdateResource
|
||||
|
||||
variable = %FarmbotCeleryScript.AST{
|
||||
args: %{label: "parent"},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :identifier,
|
||||
meta: nil
|
||||
}
|
||||
|
||||
update = %{"plant_stage" => "removed"}
|
||||
|
||||
case(variable) do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
res ->
|
||||
raise("Resource error. Please notfiy support: \#{inspect(res)}")
|
||||
end
|
||||
end
|
||||
]
|
||||
end
|
||||
]
|
||||
""")
|
||||
end
|
||||
|
||||
test "`update_resource`: Multiple fields of `resource` type." do
|
||||
compiled =
|
||||
"test/fixtures/update_resource_multi.json"
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|> AST.decode()
|
||||
|> compile()
|
||||
|
||||
assert compiled ==
|
||||
strip_nl("""
|
||||
[
|
||||
fn params ->
|
||||
_ = inspect(params)
|
||||
better_params = %{}
|
||||
|
||||
[
|
||||
fn ->
|
||||
me = FarmbotCeleryScript.Compiler.UpdateResource
|
||||
|
||||
variable = %FarmbotCeleryScript.AST{
|
||||
args: %{resource_id: 23, resource_type: "Plant"},
|
||||
body: [],
|
||||
comment: nil,
|
||||
kind: :resource,
|
||||
meta: nil
|
||||
}
|
||||
|
||||
update = %{"plant_stage" => "planted", "r" => 23}
|
||||
|
||||
case(variable) do
|
||||
%AST{kind: :identifier} ->
|
||||
args = Map.fetch!(variable, :args)
|
||||
label = Map.fetch!(args, :label)
|
||||
resource = Map.fetch!(better_params, label)
|
||||
me.do_update(resource, update)
|
||||
|
||||
%AST{kind: :point} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
%AST{kind: :resource} ->
|
||||
me.do_update(variable.args(), update)
|
||||
|
||||
res ->
|
||||
raise("Resource error. Please notfiy support: \#{inspect(res)}")
|
||||
end
|
||||
end
|
||||
]
|
||||
end
|
||||
]
|
||||
""")
|
||||
end
|
||||
|
||||
defp compile(ast) do
|
||||
ast
|
||||
|> Compiler.compile_ast([])
|
||||
|
|
|
@ -3,7 +3,10 @@ defmodule FarmbotCeleryScript.Corpus.NodeTest do
|
|||
alias FarmbotCeleryScript.Corpus
|
||||
|
||||
test "inspect" do
|
||||
assert "Sequence(version, locals) [calibrate, change_ownership, check_updates, dump_info, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, resource_update, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]" =
|
||||
inspect(Corpus.sequence())
|
||||
a =
|
||||
"Sequence(version, locals) [assertion, calibrate, change_ownership, check_updates, emergency_lock, emergency_unlock, execute, execute_script, factory_reset, find_home, flash_firmware, home, install_farmware, install_first_party_farmware, _if, move_absolute, move_relative, power_off, read_pin, read_status, reboot, remove_farmware, update_resource, send_message, set_servo_angle, set_user_env, sync, take_photo, toggle_pin, update_farmware, wait, write_pin, zero]"
|
||||
|
||||
b = inspect(Corpus.sequence())
|
||||
assert a == b
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
defmodule FarmbotCeleryScript.SchedulerTest do
|
||||
use ExUnit.Case
|
||||
use Mimic
|
||||
alias FarmbotCeleryScript.{Scheduler, AST}
|
||||
alias Farmbot.TestSupport.CeleryScript.TestSysCalls
|
||||
alias FarmbotCeleryScript.SysCalls.Stubs
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
setup do
|
||||
{:ok, shim} = TestSysCalls.checkout()
|
||||
{:ok, sch} = Scheduler.start_link([registry_name: :"#{:random.uniform()}"], [])
|
||||
[shim: shim, sch: sch]
|
||||
end
|
||||
setup :set_mimic_global
|
||||
setup :verify_on_exit!
|
||||
|
||||
test "schedules a sequence to run in the future" do
|
||||
expect(Stubs, :read_pin, 1, fn _num, _mode ->
|
||||
23
|
||||
end)
|
||||
|
||||
{:ok, sch} =
|
||||
Scheduler.start_link([registry_name: :"#{:random.uniform()}"], [])
|
||||
|
||||
test "schedules a sequence to run in the future", %{sch: sch} do
|
||||
ast =
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("hello world")
|
||||
|> AST.Factory.read_pin(9, 0)
|
||||
|
||||
pid = self()
|
||||
|
||||
:ok =
|
||||
TestSysCalls.handle(TestSysCalls, fn
|
||||
:read_pin, args ->
|
||||
send(pid, {:read_pin, args})
|
||||
1
|
||||
end)
|
||||
|
||||
scheduled_time = DateTime.utc_now() |> DateTime.add(100, :millisecond)
|
||||
# msg = "[info] Next execution is ready for execution: now"
|
||||
{:ok, _} = Scheduler.schedule(sch, ast, scheduled_time, %{})
|
||||
|
||||
# Hack to force the scheduler to checkup instead of waiting the normal 15 seconds
|
||||
send(sch, :checkup)
|
||||
assert_receive {:read_pin, [9, 0]}, 1000
|
||||
assert capture_log(fn ->
|
||||
send(sch, :checkup)
|
||||
# Sorry.
|
||||
Process.sleep(1100)
|
||||
end) =~ "[info] Next execution is ready for execution: now"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,178 +1,240 @@
|
|||
defmodule FarmbotCeleryScript.SysCallsTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Farmbot.TestSupport.CeleryScript.TestSysCalls
|
||||
alias FarmbotCeleryScript.{AST, SysCalls}
|
||||
use Mimic
|
||||
|
||||
setup do
|
||||
{:ok, shim} = TestSysCalls.checkout()
|
||||
[shim: shim]
|
||||
alias FarmbotCeleryScript.{
|
||||
SysCalls,
|
||||
SysCalls.Stubs,
|
||||
AST
|
||||
}
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
test "point, OK" do
|
||||
expect(Stubs, :point, 1, fn _kind, 1 ->
|
||||
%{x: 100, y: 200, z: 300}
|
||||
end)
|
||||
|
||||
result1 = SysCalls.point(Stubs, "Peripheral", 1)
|
||||
assert %{x: 100, y: 200, z: 300} == result1
|
||||
end
|
||||
|
||||
test "point", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim, %{x: 100, y: 200, z: 300})
|
||||
assert %{x: 100, y: 200, z: 300} = SysCalls.point(TestSysCalls, "Peripheral", 1)
|
||||
assert_receive {:point, ["Peripheral", 1]}
|
||||
test "point, NO" do
|
||||
expect(Stubs, :point, 1, fn _kind, 0 ->
|
||||
:whatever
|
||||
end)
|
||||
|
||||
:ok = shim_fun_error(shim, "point error")
|
||||
|
||||
assert {:error, "point error"} == SysCalls.point(TestSysCalls, "Peripheral", 1)
|
||||
boom = fn -> SysCalls.point(Stubs, "Peripheral", 0) end
|
||||
assert_raise FarmbotCeleryScript.RuntimeError, boom
|
||||
end
|
||||
|
||||
test "move_absolute", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.move_absolute(TestSysCalls, 1, 2, 3, 4)
|
||||
assert_receive {:move_absolute, [1, 2, 3, 4]}
|
||||
test "point groups failure" do
|
||||
expect(Stubs, :find_points_via_group, 1, fn _id ->
|
||||
:whatever
|
||||
end)
|
||||
|
||||
:ok = shim_fun_error(shim, "move failed!")
|
||||
|
||||
assert {:error, "move failed!"} == SysCalls.move_absolute(TestSysCalls, 1, 2, 3, 4)
|
||||
boom = fn -> SysCalls.find_points_via_group(Stubs, :something_else) end
|
||||
assert_raise FarmbotCeleryScript.RuntimeError, boom
|
||||
end
|
||||
|
||||
test "get current positions", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim, 100.00)
|
||||
assert 100.00 = SysCalls.get_current_x(TestSysCalls)
|
||||
assert 100.00 = SysCalls.get_current_y(TestSysCalls)
|
||||
assert 100.00 = SysCalls.get_current_z(TestSysCalls)
|
||||
test "point groups success" do
|
||||
expect(Stubs, :find_points_via_group, 1, fn _id ->
|
||||
%{point_ids: [1, 2, 3]}
|
||||
end)
|
||||
|
||||
assert_receive {:get_current_x, []}
|
||||
assert_receive {:get_current_y, []}
|
||||
assert_receive {:get_current_z, []}
|
||||
|
||||
:ok = shim_fun_error(shim, "firmware error")
|
||||
|
||||
assert {:error, "firmware error"} == SysCalls.get_current_x(TestSysCalls)
|
||||
assert {:error, "firmware error"} == SysCalls.get_current_y(TestSysCalls)
|
||||
assert {:error, "firmware error"} == SysCalls.get_current_z(TestSysCalls)
|
||||
pg = %{point_ids: [1, 2, 3]}
|
||||
result = SysCalls.find_points_via_group(Stubs, 456)
|
||||
assert result == pg
|
||||
end
|
||||
|
||||
test "write_pin", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.write_pin(TestSysCalls, 1, 0, 1)
|
||||
assert :ok = SysCalls.write_pin(TestSysCalls, %{type: "boxled", id: 4}, 0, 1)
|
||||
assert :ok = SysCalls.write_pin(TestSysCalls, %{type: "boxled", id: 3}, 1, 123)
|
||||
test "move_absolute, OK" do
|
||||
expect(Stubs, :move_absolute, 1, fn 1, 2, 3, 4 ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
assert_receive {:write_pin, [1, 0, 1]}
|
||||
assert_receive {:write_pin, [%{type: "boxled", id: 4}, 0, 1]}
|
||||
assert_receive {:write_pin, [%{type: "boxled", id: 3}, 1, 123]}
|
||||
|
||||
:ok = shim_fun_error(shim, "firmware error")
|
||||
|
||||
assert {:error, "firmware error"} == SysCalls.write_pin(TestSysCalls, 1, 0, 1)
|
||||
assert :ok = SysCalls.move_absolute(Stubs, 1, 2, 3, 4)
|
||||
end
|
||||
|
||||
test "read_pin", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim, 1)
|
||||
assert 1 == SysCalls.read_pin(TestSysCalls, 10, 0)
|
||||
assert 1 == SysCalls.read_pin(TestSysCalls, 77, nil)
|
||||
assert_receive {:read_pin, [10, 0]}
|
||||
assert_receive {:read_pin, [77, nil]}
|
||||
test "move_absolute, NO" do
|
||||
expect(Stubs, :move_absolute, 1, fn 1, 2, 3, 4 ->
|
||||
{:error, "move failed!"}
|
||||
end)
|
||||
|
||||
:ok = shim_fun_error(shim, "firmware error")
|
||||
|
||||
assert {:error, "firmware error"} == SysCalls.read_pin(TestSysCalls, 1, 0)
|
||||
assert {:error, "move failed!"} ==
|
||||
SysCalls.move_absolute(Stubs, 1, 2, 3, 4)
|
||||
end
|
||||
|
||||
test "wait", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.wait(TestSysCalls, 1000)
|
||||
assert_receive {:wait, [1000]}
|
||||
test "get positions, OK" do
|
||||
expect(Stubs, :get_current_x, 1, fn -> 100.00 end)
|
||||
expect(Stubs, :get_current_y, 1, fn -> 200.00 end)
|
||||
expect(Stubs, :get_current_z, 1, fn -> 300.00 end)
|
||||
assert 100.00 = SysCalls.get_current_x(Stubs)
|
||||
assert 200.00 = SysCalls.get_current_y(Stubs)
|
||||
assert 300.00 = SysCalls.get_current_z(Stubs)
|
||||
end
|
||||
|
||||
test "named_pin", %{shim: shim} do
|
||||
test "get positions, KO" do
|
||||
expect(Stubs, :get_current_x, 1, fn -> {:error, "L"} end)
|
||||
expect(Stubs, :get_current_y, 1, fn -> {:error, "O"} end)
|
||||
expect(Stubs, :get_current_z, 1, fn -> {:error, "L"} end)
|
||||
|
||||
assert {:error, "L"} == SysCalls.get_current_x(Stubs)
|
||||
assert {:error, "O"} == SysCalls.get_current_y(Stubs)
|
||||
assert {:error, "L"} == SysCalls.get_current_z(Stubs)
|
||||
end
|
||||
|
||||
test "write_pin" do
|
||||
err = {:error, "firmware error?"}
|
||||
|
||||
expect(Stubs, :write_pin, 4, fn pin_num, _, _ ->
|
||||
if pin_num == 66 do
|
||||
err
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
assert :ok = SysCalls.write_pin(Stubs, 1, 0, 1)
|
||||
assert :ok = SysCalls.write_pin(Stubs, %{type: "boxled", id: 4}, 0, 1)
|
||||
assert :ok = SysCalls.write_pin(Stubs, %{type: "boxled", id: 3}, 1, 123)
|
||||
assert err == SysCalls.write_pin(Stubs, 66, 0, 1)
|
||||
end
|
||||
|
||||
test "read_pin" do
|
||||
expect(Stubs, :read_pin, 3, fn num, _mode ->
|
||||
if num == 1 do
|
||||
{:error, "firmware error"}
|
||||
else
|
||||
num * 2
|
||||
end
|
||||
end)
|
||||
|
||||
assert 20 == SysCalls.read_pin(Stubs, 10, 0)
|
||||
assert 30 == SysCalls.read_pin(Stubs, 15, nil)
|
||||
assert {:error, "firmware error"} == SysCalls.read_pin(Stubs, 1, 0)
|
||||
end
|
||||
|
||||
test "wait" do
|
||||
expect(Stubs, :wait, fn ms ->
|
||||
if ms == 1000 do
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
assert :ok = SysCalls.wait(Stubs, 1000)
|
||||
end
|
||||
|
||||
test "named_pin" do
|
||||
err = {:error, "error finding resource"}
|
||||
|
||||
expect(Stubs, :named_pin, 5, fn kind, num ->
|
||||
hmm = {kind, num}
|
||||
|
||||
case hmm do
|
||||
{"Peripheral", 5} -> 44
|
||||
{"Sensor", 1999} -> 55
|
||||
{"BoxLed", 3} -> %{type: "BoxLed", id: 3}
|
||||
{"BoxLed", 4} -> %{type: "BoxLed", id: 4}
|
||||
{"Peripheral", 888} -> err
|
||||
end
|
||||
end)
|
||||
|
||||
# Peripheral and Sensor are on the Arduino
|
||||
:ok = shim_fun_ok(shim, 44)
|
||||
assert 44 == SysCalls.named_pin(TestSysCalls, "Peripheral", 5)
|
||||
assert 44 == SysCalls.named_pin(TestSysCalls, "Sensor", 1999)
|
||||
assert 44 == SysCalls.named_pin(Stubs, "Peripheral", 5)
|
||||
assert 55 == SysCalls.named_pin(Stubs, "Sensor", 1999)
|
||||
|
||||
# BoxLed is on the GPIO
|
||||
:ok = shim_fun_ok(shim, %{type: "BoxLed", id: 3})
|
||||
assert %{type: "BoxLed", id: 3} == SysCalls.named_pin(TestSysCalls, "BoxLed", 3)
|
||||
|
||||
:ok = shim_fun_ok(shim, %{type: "BoxLed", id: 4})
|
||||
assert %{type: "BoxLed", id: 4} == SysCalls.named_pin(TestSysCalls, "BoxLed", 4)
|
||||
assert %{type: "BoxLed", id: 3} ==
|
||||
SysCalls.named_pin(Stubs, "BoxLed", 3)
|
||||
|
||||
assert_receive {:named_pin, ["Peripheral", 5]}
|
||||
assert_receive {:named_pin, ["Sensor", 1999]}
|
||||
assert_receive {:named_pin, ["BoxLed", 3]}
|
||||
assert_receive {:named_pin, ["BoxLed", 4]}
|
||||
assert %{type: "BoxLed", id: 4} ==
|
||||
SysCalls.named_pin(Stubs, "BoxLed", 4)
|
||||
|
||||
:ok = shim_fun_error(shim, "error finding resource")
|
||||
|
||||
assert {:error, "error finding resource"} ==
|
||||
SysCalls.named_pin(TestSysCalls, "Peripheral", 888)
|
||||
assert err == SysCalls.named_pin(Stubs, "Peripheral", 888)
|
||||
end
|
||||
|
||||
test "send_message", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.send_message(TestSysCalls, "success", "hello world", ["email"])
|
||||
assert_receive {:send_message, ["success", "hello world", ["email"]]}
|
||||
test "send_message" do
|
||||
err = {:error, "email machine broke"}
|
||||
|
||||
:ok = shim_fun_error(shim, "email machine broke")
|
||||
expect(Stubs, :send_message, 2, fn type, _msg, _chans ->
|
||||
if type == "error" do
|
||||
err
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:error, "email machine broke"} ==
|
||||
SysCalls.send_message(TestSysCalls, "error", "goodbye world", ["email"])
|
||||
assert :ok =
|
||||
SysCalls.send_message(Stubs, "success", "hello world", ["email"])
|
||||
|
||||
assert err ==
|
||||
SysCalls.send_message(Stubs, "error", "goodbye world", ["email"])
|
||||
end
|
||||
|
||||
test "find_home", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.find_home(TestSysCalls, "x")
|
||||
assert_receive {:find_home, ["x"]}
|
||||
test "find_home" do
|
||||
err = {:error, "home lost"}
|
||||
|
||||
:ok = shim_fun_error(shim, "home lost")
|
||||
expect(Stubs, :find_home, 2, fn axis ->
|
||||
if axis == "x" do
|
||||
:ok
|
||||
else
|
||||
err
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:error, "home lost"} == SysCalls.find_home(TestSysCalls, "x")
|
||||
assert :ok = SysCalls.find_home(Stubs, "x")
|
||||
assert err == SysCalls.find_home(Stubs, "z")
|
||||
end
|
||||
|
||||
test "execute_script", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
assert :ok = SysCalls.execute_script(TestSysCalls, "take-photo", %{})
|
||||
assert_receive {:execute_script, ["take-photo", %{}]}
|
||||
test "execute_script" do
|
||||
err = {:error, "not installed"}
|
||||
|
||||
:ok = shim_fun_error(shim, "not installed")
|
||||
expect(Stubs, :execute_script, 2, fn "take-photo", env ->
|
||||
if Map.get(env, :error) do
|
||||
err
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:error, "not installed"} == SysCalls.execute_script(TestSysCalls, "take-photo", %{})
|
||||
assert :ok = SysCalls.execute_script(Stubs, "take-photo", %{})
|
||||
assert err == SysCalls.execute_script(Stubs, "take-photo", %{error: true})
|
||||
end
|
||||
|
||||
test "set_servo_angle errors", %{shim: shim} do
|
||||
:ok = shim_fun_ok(shim)
|
||||
arg0 = [5, 40]
|
||||
assert :ok = SysCalls.set_servo_angle(TestSysCalls, "set_servo_angle", arg0)
|
||||
assert_receive {:set_servo_angle, arg0}
|
||||
test "set_servo_angle errors" do
|
||||
error = {:error, "boom"}
|
||||
|
||||
arg1 = [40, -5]
|
||||
:ok = shim_fun_error(shim, "boom")
|
||||
assert {:error, "boom"} == SysCalls.set_servo_angle(TestSysCalls, "set_servo_angle", arg1)
|
||||
expect(Stubs, :set_servo_angle, 2, fn num, _val ->
|
||||
if num == 5 do
|
||||
:ok
|
||||
else
|
||||
error
|
||||
end
|
||||
end)
|
||||
|
||||
assert error == SysCalls.set_servo_angle(Stubs, 40, -5)
|
||||
assert :ok == SysCalls.set_servo_angle(Stubs, 5, 40)
|
||||
end
|
||||
|
||||
test "get_sequence", %{shim: shim} do
|
||||
:ok =
|
||||
shim_fun_ok(shim, %AST{
|
||||
kind: :sequence,
|
||||
args: %{locals: %AST{kind: :scope_declaration, args: %{}}}
|
||||
})
|
||||
test "get_sequence" do
|
||||
nothing = %AST{
|
||||
kind: "nothing",
|
||||
args: {},
|
||||
body: []
|
||||
}
|
||||
|
||||
assert %{} = SysCalls.get_sequence(TestSysCalls, 123)
|
||||
assert_receive {:get_sequence, [123]}
|
||||
err = {:error, "sequence not found"}
|
||||
|
||||
:ok = shim_fun_error(shim, "sequence not found")
|
||||
expect(Stubs, :get_sequence, 2, fn sequence_id ->
|
||||
if sequence_id == 321 do
|
||||
err
|
||||
else
|
||||
nothing
|
||||
end
|
||||
end)
|
||||
|
||||
assert {:error, "sequence not found"} == SysCalls.get_sequence(TestSysCalls, 123)
|
||||
end
|
||||
|
||||
def shim_fun_ok(shim, val \\ :ok) do
|
||||
pid = self()
|
||||
|
||||
:ok =
|
||||
TestSysCalls.handle(shim, fn kind, args ->
|
||||
send(pid, {kind, args})
|
||||
val
|
||||
end)
|
||||
end
|
||||
|
||||
def shim_fun_error(shim, val) when is_binary(val) do
|
||||
:ok =
|
||||
TestSysCalls.handle(shim, fn _kind, _args ->
|
||||
{:error, val}
|
||||
end)
|
||||
assert nothing == SysCalls.get_sequence(Stubs, 123)
|
||||
assert err == SysCalls.get_sequence(Stubs, 321)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
defmodule FarmbotCeleryScriptTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias Farmbot.TestSupport.CeleryScript.TestSysCalls
|
||||
use Mimic
|
||||
|
||||
setup do
|
||||
{:ok, _shim} = TestSysCalls.checkout()
|
||||
:ok
|
||||
end
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
setup :verify_on_exit!
|
||||
|
||||
test "uses default values when no parameter is found" do
|
||||
sequence_ast =
|
||||
|
@ -24,7 +26,7 @@ defmodule FarmbotCeleryScriptTest do
|
|||
label: "foo",
|
||||
default_value: %{
|
||||
kind: :coordinate,
|
||||
args: %{x: 129, y: 129, z: 129}
|
||||
args: %{x: 12, y: 11, z: 10}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,19 +54,18 @@ defmodule FarmbotCeleryScriptTest do
|
|||
|
||||
me = self()
|
||||
|
||||
:ok =
|
||||
TestSysCalls.handle(TestSysCalls, fn
|
||||
:move_absolute, args ->
|
||||
send(me, {:move_absolute, args})
|
||||
:ok
|
||||
expect(Stubs, :coordinate, 2, fn x, y, z ->
|
||||
%{x: x, y: y, z: z}
|
||||
end)
|
||||
|
||||
:coordinate, [x, y, z] ->
|
||||
%{x: x, y: y, z: z}
|
||||
end)
|
||||
expect(Stubs, :move_absolute, 1, fn _x, _y, _z, _s ->
|
||||
:ok
|
||||
end)
|
||||
|
||||
_ = FarmbotCeleryScript.execute(sequence_ast, me)
|
||||
assert_receive {:step_complete, ^me, :ok}
|
||||
assert_receive {:move_absolute, [129, 129, 129, 921]}
|
||||
capture_log(fn ->
|
||||
result = FarmbotCeleryScript.execute(sequence_ast, me)
|
||||
assert :ok == result
|
||||
end) =~ "[error] CeleryScript syscall stubbed: log"
|
||||
end
|
||||
|
||||
test "syscall errors" do
|
||||
|
@ -78,13 +79,12 @@ defmodule FarmbotCeleryScriptTest do
|
|||
}
|
||||
|> AST.decode()
|
||||
|
||||
:ok =
|
||||
TestSysCalls.handle(TestSysCalls, fn
|
||||
:read_pin, _ -> {:error, "failed to read pin!"}
|
||||
end)
|
||||
expect(Stubs, :read_pin, 1, fn _, _ -> {:error, "failed to read pin!"} end)
|
||||
result = FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
||||
assert {:error, "failed to read pin!"} = result
|
||||
|
||||
assert {:error, "failed to read pin!"} = FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
||||
assert_receive {:step_complete, ^execute_ast, {:error, "failed to read pin!"}}
|
||||
assert_receive {:step_complete, ^execute_ast,
|
||||
{:error, "failed to read pin!"}}
|
||||
end
|
||||
|
||||
test "regular exceptions still occur" do
|
||||
|
@ -98,12 +98,17 @@ defmodule FarmbotCeleryScriptTest do
|
|||
}
|
||||
|> AST.decode()
|
||||
|
||||
:ok =
|
||||
TestSysCalls.handle(TestSysCalls, fn
|
||||
:read_pin, _ -> raise("big oops")
|
||||
expect(Stubs, :read_pin, fn _, _ ->
|
||||
raise("big oops")
|
||||
end)
|
||||
|
||||
io =
|
||||
capture_io(:stderr, fn ->
|
||||
assert {:error, "big oops"} ==
|
||||
FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
||||
end)
|
||||
|
||||
assert {:error, "big oops"} == FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
||||
assert io =~ "CeleryScript Exception"
|
||||
assert_receive {:step_complete, ^execute_ast, {:error, "big oops"}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"default_value": {
|
||||
"args": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 3
|
||||
},
|
||||
"kind": "coordinate"
|
||||
},
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "parameter_declaration"
|
||||
}
|
||||
],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "identifier"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "meta.my_prop",
|
||||
"value": "whatever"
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"default_value": {
|
||||
"args": {
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 3
|
||||
},
|
||||
"kind": "coordinate"
|
||||
},
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "parameter_declaration"
|
||||
}
|
||||
],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"label": "parent"
|
||||
},
|
||||
"kind": "identifier"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "plant_stage",
|
||||
"value": "removed"
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"resource_id": 0,
|
||||
"resource_type": "Device"
|
||||
},
|
||||
"kind": "resource"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "mounted_tool_id",
|
||||
"value": 12161
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"args": {
|
||||
"locals": {
|
||||
"args": {},
|
||||
"body": [],
|
||||
"kind": "scope_declaration"
|
||||
},
|
||||
"version": -999
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"resource": {
|
||||
"args": {
|
||||
"resource_id": 23,
|
||||
"resource_type": "Plant"
|
||||
},
|
||||
"kind": "resource"
|
||||
}
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"args": {
|
||||
"label": "plant_stage",
|
||||
"value": "planted"
|
||||
},
|
||||
"kind": "pair"
|
||||
},
|
||||
{
|
||||
"args": {
|
||||
"label": "r",
|
||||
"value": 23
|
||||
},
|
||||
"kind": "pair"
|
||||
}
|
||||
],
|
||||
"kind": "update_resource"
|
||||
}
|
||||
],
|
||||
"kind": "sequence"
|
||||
}
|
|
@ -1 +1,3 @@
|
|||
Application.ensure_all_started(:mimic)
|
||||
Mimic.copy(FarmbotCeleryScript.SysCalls.Stubs)
|
||||
ExUnit.start()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[
|
||||
import_deps: [:ecto],
|
||||
line_length: 80,
|
||||
inputs: [
|
||||
"*.{ex,exs}",
|
||||
"{config,priv,test}/**/*.{ex,exs}",
|
||||
|
|
|
@ -1,51 +1,52 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmEvent, checkup_time_ms: 10_000
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmEvent,
|
||||
checkup_time_ms: 10_000
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.RegimenInstance,
|
||||
checkup_time_ms: 10_000
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: "/tmp/farmware"
|
||||
config :farmbot_core,
|
||||
FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation,
|
||||
error_retry_time_ms: 30_000,
|
||||
install_dir: "/tmp/farmware"
|
||||
|
||||
config :farmbot_core, FarmbotCore.FarmwareRuntime, runtime_dir: "/tmp/farmware_runtime"
|
||||
config :farmbot_core, FarmbotCore.FarmwareRuntime,
|
||||
runtime_dir: "/tmp/farmware_runtime"
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.PinBinding,
|
||||
gpio_handler: FarmbotCore.PinBindingWorker.StubGPIOHandler,
|
||||
error_retry_time_ms: 30_000
|
||||
|
||||
config :farmbot_core, Elixir.FarmbotCore.AssetWorker.FarmbotCore.Asset.PublicKey,
|
||||
ssh_handler: FarmbotCore.PublicKeyHandler.StubSSHHandler
|
||||
config :farmbot_core,
|
||||
Elixir.FarmbotCore.AssetWorker.FarmbotCore.Asset.PublicKey,
|
||||
ssh_handler: FarmbotCore.PublicKeyHandler.StubSSHHandler
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetMonitor, checkup_time_ms: 30_000
|
||||
|
||||
config :farmbot_core, FarmbotCore.Leds, gpio_handler: FarmbotCore.Leds.StubHandler
|
||||
config :farmbot_core, FarmbotCore.Leds,
|
||||
gpio_handler: FarmbotCore.Leds.StubHandler
|
||||
|
||||
config :farmbot_core, FarmbotCore.JSON, json_parser: FarmbotCore.JSON.JasonParser
|
||||
config :farmbot_core, FarmbotCore.JSON,
|
||||
json_parser: FarmbotCore.JSON.JasonParser
|
||||
|
||||
config :farmbot_core, FarmbotCore.BotState.FileSystem,
|
||||
root_dir: "/tmp/farmbot",
|
||||
sleep_time: 200
|
||||
|
||||
config :farmbot_core, FarmbotCore.EctoMigrator,
|
||||
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
|
||||
default_firmware_io_logs: false,
|
||||
default_server: "https://my.farm.bot",
|
||||
default_dns_name: "my.farm.bot",
|
||||
default_ntp_server_1: "0.pool.ntp.org",
|
||||
default_ntp_server_2: "1.pool.ntp.org",
|
||||
default_currently_on_beta:
|
||||
String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta")
|
||||
String.contains?(
|
||||
to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')),
|
||||
"beta"
|
||||
)
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector, expected_names: []
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotFirmware.NullReset
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
config :farmbot_firmware, FarmbotFirmware, reset: FarmbotCore.FirmwareResetter
|
||||
|
||||
import_config "ecto.exs"
|
||||
import_config "logger.exs"
|
||||
|
|
|
@ -2,8 +2,3 @@ use Mix.Config
|
|||
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 5
|
||||
|
|
|
@ -2,7 +2,11 @@ use Mix.Config
|
|||
config :ecto, json_library: FarmbotCore.JSON
|
||||
|
||||
config :farmbot_core,
|
||||
ecto_repos: [FarmbotCore.Config.Repo, FarmbotCore.Logger.Repo, FarmbotCore.Asset.Repo]
|
||||
ecto_repos: [
|
||||
FarmbotCore.Config.Repo,
|
||||
FarmbotCore.Logger.Repo,
|
||||
FarmbotCore.Asset.Repo
|
||||
]
|
||||
|
||||
config :farmbot_core, FarmbotCore.Config.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
use Mix.Config
|
||||
config :logger, level: :warn
|
||||
|
||||
# must be lower than other timers
|
||||
# To ensure other timers have time to timeout
|
||||
config :farmbot_core, FarmbotCore.AssetMonitor, checkup_time_ms: 500
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmEvent, checkup_time_ms: 1000
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmEvent,
|
||||
checkup_time_ms: 1000
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.RegimenInstance,
|
||||
checkup_time_ms: 1000
|
||||
|
||||
config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
|
||||
sys_calls: Farmbot.TestSupport.CeleryScript.TestSysCalls
|
||||
sys_calls: FarmbotCeleryScript.SysCalls.Stubs
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 0
|
||||
|
||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||
firmware_flash_attempt_threshold: 0
|
||||
|
||||
if Mix.env() == :test do
|
||||
config :ex_unit, capture_logs: true
|
||||
mapper = fn mod -> config :farmbot_core, mod, children: [] end
|
||||
|
||||
list = [
|
||||
FarmbotCore,
|
||||
FarmbotCore.StorageSupervisor,
|
||||
FarmbotCore.Asset.Supervisor,
|
||||
FarmbotCore.BotState.Supervisor,
|
||||
FarmbotCore.Config.Supervisor,
|
||||
FarmbotCore.Logger.Supervisor
|
||||
]
|
||||
|
||||
Enum.map(list, mapper)
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"coverage_options": {
|
||||
"treat_no_relevant_lines_as_covered": true,
|
||||
"minimum_coverage": 25
|
||||
}
|
||||
}
|
|
@ -14,8 +14,11 @@ defmodule FarmbotCore do
|
|||
def start(_, args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||
|
||||
def init([]) do
|
||||
Supervisor.init(children(), [strategy: :one_for_one])
|
||||
end
|
||||
|
||||
children = [
|
||||
def children do
|
||||
default = [
|
||||
FarmbotCore.Leds,
|
||||
FarmbotCore.EctoMigrator,
|
||||
FarmbotCore.BotState.Supervisor,
|
||||
|
@ -24,9 +27,13 @@ defmodule FarmbotCore do
|
|||
FarmbotCore.FirmwareOpenTask,
|
||||
FarmbotCore.FirmwareEstopTimer,
|
||||
# Also error handling for a transport not starting ?
|
||||
{FarmbotFirmware, transport: FarmbotFirmware.StubTransport, side_effects: FarmbotCore.FirmwareSideEffects},
|
||||
{FarmbotFirmware,
|
||||
transport: FarmbotFirmware.StubTransport,
|
||||
side_effects: FarmbotCore.FirmwareSideEffects,
|
||||
reset: FarmbotCore.FirmwareResetter},
|
||||
FarmbotCeleryScript.Scheduler
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
config = (Application.get_env(:farmbot_ext, __MODULE__) || [])
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,16 +6,15 @@ defmodule FarmbotCore.Asset do
|
|||
"""
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
Repo,
|
||||
CriteriaRetriever,
|
||||
Device,
|
||||
DeviceCert,
|
||||
DiagnosticDump,
|
||||
FarmwareEnv,
|
||||
FirstPartyFarmware,
|
||||
FarmwareInstallation,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FbosConfig,
|
||||
FirmwareConfig,
|
||||
FirstPartyFarmware,
|
||||
Peripheral,
|
||||
PinBinding,
|
||||
Point,
|
||||
|
@ -23,10 +22,11 @@ defmodule FarmbotCore.Asset do
|
|||
PublicKey,
|
||||
Regimen,
|
||||
RegimenInstance,
|
||||
Sequence,
|
||||
Repo,
|
||||
Sensor,
|
||||
SensorReading,
|
||||
Tool
|
||||
Sequence,
|
||||
Tool,
|
||||
}
|
||||
|
||||
alias FarmbotCore.AssetSupervisor
|
||||
|
@ -54,6 +54,7 @@ defmodule FarmbotCore.Asset do
|
|||
if device = Repo.get_by(Device, id: id) do
|
||||
Repo.delete!(device)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -61,11 +62,6 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
## Begin FarmEvent
|
||||
|
||||
@doc "Returns all FarmEvents"
|
||||
def list_farm_events do
|
||||
Repo.all(FarmEvent)
|
||||
end
|
||||
|
||||
def new_farm_event!(params) do
|
||||
%FarmEvent{}
|
||||
|> FarmEvent.changeset(params)
|
||||
|
@ -78,13 +74,14 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_farm_event!(farm_event, params) do
|
||||
farm_event =
|
||||
farm_event |>
|
||||
FarmEvent.changeset(params)
|
||||
farm_event =
|
||||
farm_event
|
||||
|> FarmEvent.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
||||
if farm_event.executable_type == "Regimen" do
|
||||
regimen_instance = get_regimen_instance(farm_event)
|
||||
|
||||
if regimen_instance do
|
||||
regimen_instance
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
|
@ -111,9 +108,11 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def get_farm_event_execution(%FarmEvent{} = farm_event, scheduled_at) do
|
||||
Repo.one(
|
||||
from e in FarmEvent.Execution,
|
||||
where: e.farm_event_local_id == ^farm_event.local_id
|
||||
and e.scheduled_at == ^scheduled_at
|
||||
from(e in FarmEvent.Execution,
|
||||
where:
|
||||
e.farm_event_local_id == ^farm_event.local_id and
|
||||
e.scheduled_at == ^scheduled_at
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -149,6 +148,7 @@ defmodule FarmbotCore.Asset do
|
|||
if fbos_config = Repo.get_by(FbosConfig, id: id) do
|
||||
Repo.delete!(fbos_config)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -177,6 +177,7 @@ defmodule FarmbotCore.Asset do
|
|||
if firmware_config = Repo.get_by(FirmwareConfig, id: id) do
|
||||
Repo.delete!(firmware_config)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -192,12 +193,19 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def get_regimen_instance(%FarmEvent{} = farm_event) do
|
||||
regimen = Repo.one(from r in Regimen, where: r.id == ^farm_event.executable_id)
|
||||
regimen && Repo.one(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id and ri.farm_event_id == ^farm_event.local_id)
|
||||
regimen = Repo.one(from(r in Regimen, where: r.id == ^farm_event.executable_id))
|
||||
|
||||
regimen &&
|
||||
Repo.one(
|
||||
from(ri in RegimenInstance,
|
||||
where: ri.regimen_id == ^regimen.local_id and ri.farm_event_id == ^farm_event.local_id
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def new_regimen_instance!(%FarmEvent{} = farm_event, params \\ %{}) do
|
||||
regimen = Repo.one!(from r in Regimen, where: r.id == ^farm_event.executable_id)
|
||||
regimen = Repo.one!(from(r in Regimen, where: r.id == ^farm_event.executable_id))
|
||||
|
||||
RegimenInstance.changeset(%RegimenInstance{}, params)
|
||||
|> Ecto.Changeset.put_assoc(:regimen, regimen)
|
||||
|> Ecto.Changeset.put_assoc(:farm_event, farm_event)
|
||||
|
@ -207,7 +215,7 @@ defmodule FarmbotCore.Asset do
|
|||
def delete_regimen_instance!(%RegimenInstance{} = ri) do
|
||||
Repo.delete!(ri)
|
||||
end
|
||||
|
||||
|
||||
def add_execution_to_regimen_instance!(%RegimenInstance{} = ri, params \\ %{}) do
|
||||
%RegimenInstance.Execution{}
|
||||
|> RegimenInstance.Execution.changeset(params)
|
||||
|
@ -217,9 +225,11 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def get_regimen_instance_execution(%RegimenInstance{} = ri, scheduled_at) do
|
||||
Repo.one(
|
||||
from e in RegimenInstance.Execution,
|
||||
where: e.regimen_instance_local_id == ^ri.local_id
|
||||
and e.scheduled_at == ^scheduled_at
|
||||
from(e in RegimenInstance.Execution,
|
||||
where:
|
||||
e.regimen_instance_local_id == ^ri.local_id and
|
||||
e.scheduled_at == ^scheduled_at
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -241,14 +251,30 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_point(point, params) do
|
||||
point
|
||||
|> Point.changeset(params)
|
||||
# TODO: RC 8 MAY 2020 - We need to hard refresh the point.
|
||||
# The CSVM appears to be caching resources. This leads
|
||||
# to problems when a user runs a sequence that has two
|
||||
# MARK AS steps.
|
||||
# NOTE: Updating the `meta` attribute is a _replace_ action
|
||||
# by default, not a merge action.
|
||||
# MORE NOTES: Mixed keys (symbol vs. string) will crash this FN.
|
||||
# Let's just stringify everything...
|
||||
new_meta = params[:meta] || params["meta"] || %{}
|
||||
old_meta = point.meta || %{}
|
||||
updated_meta = Map.merge(old_meta, new_meta)
|
||||
clean_params = params
|
||||
|> Map.merge(%{meta: updated_meta})
|
||||
|> Enum.map(fn {k, v} -> {"#{k}", v} end)
|
||||
|> Map.new()
|
||||
|
||||
Repo.get_by(Point, id: point.id)
|
||||
|> Point.changeset(clean_params)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc "Returns all points matching Point.pointer_type"
|
||||
def get_all_points_by_type(type, order_by \\ "random") do
|
||||
(from p in Point, where: p.pointer_type == ^type and is_nil(p.discarded_at))
|
||||
from(p in Point, where: p.pointer_type == ^type and is_nil(p.discarded_at))
|
||||
|> Repo.all()
|
||||
|> sort_points(order_by)
|
||||
end
|
||||
|
@ -257,7 +283,7 @@ defmodule FarmbotCore.Asset do
|
|||
points
|
||||
|> Enum.group_by(&group_points_by(&1, order_by))
|
||||
|> Enum.sort(&group_sort(&1, &2, order_by))
|
||||
|> Enum.map(fn({_group_index, group}) -> Enum.sort(group, &sort_points(&1, &2, order_by)) end)
|
||||
|> Enum.map(fn {_group_index, group} -> Enum.sort(group, &sort_points(&1, &2, order_by)) end)
|
||||
|> List.flatten()
|
||||
end
|
||||
|
||||
|
@ -272,7 +298,6 @@ defmodule FarmbotCore.Asset do
|
|||
def group_sort({lgroup, _}, {rgroup, _}, "yx_descending"), do: lgroup >= rgroup
|
||||
def group_sort(_, _, "random"), do: Enum.random([true, false])
|
||||
|
||||
|
||||
def sort_points(%{y: ly}, %{y: ry}, "xy_ascending"), do: ly <= ry
|
||||
def sort_points(%{y: ly}, %{y: ry}, "xy_descending"), do: ly >= ry
|
||||
def sort_points(%{x: lx}, %{x: rx}, "yx_ascending"), do: lx <= rx
|
||||
|
@ -285,15 +310,15 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def get_point_group(params) do
|
||||
case Repo.get_by(PointGroup, params) do
|
||||
nil ->
|
||||
nil ->
|
||||
nil
|
||||
|
||||
%{sort_type: nil} = group ->
|
||||
group
|
||||
%{sort_type: nil} = group ->
|
||||
group
|
||||
|
||||
%{point_ids: unsorted, sort_type: sort_by} = point_group ->
|
||||
sorted =
|
||||
Repo.all(from p in Point, where: p.id in ^unsorted)
|
||||
sorted =
|
||||
Repo.all(from(p in Point, where: p.id in ^unsorted))
|
||||
|> sort_points(sort_by)
|
||||
|> Enum.map(&Map.fetch!(&1, :id))
|
||||
|
||||
|
@ -301,6 +326,30 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
end
|
||||
|
||||
def find_points_via_group(id) do
|
||||
case Repo.get_by(PointGroup, id: id) do
|
||||
%{id: _id, sort_type: sort_by} = point_group ->
|
||||
# I don't like this because it makes the code
|
||||
# harder to understand.
|
||||
# We are essentially patching the value of
|
||||
# point_group.point_ids with additional IDs.
|
||||
# Keep this in mind when debugging sequences
|
||||
# that deal with point groups- the point_ids
|
||||
# value is not a reflection of what is in
|
||||
# the DB / API.
|
||||
sorted = CriteriaRetriever.run(point_group)
|
||||
|> sort_points(sort_by || "xy_ascending")
|
||||
|> Enum.map(fn point -> point.id end)
|
||||
%{ point_group | point_ids: sorted }
|
||||
other ->
|
||||
# Swallow all other errors
|
||||
a = inspect(id)
|
||||
b = inspect(other)
|
||||
Logger.debug("Unexpected point group #{a} #{b}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def new_point_group!(params) do
|
||||
%PointGroup{}
|
||||
|> PointGroup.changeset(params)
|
||||
|
@ -308,13 +357,13 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def update_point_group!(point_group, params) do
|
||||
updated =
|
||||
updated =
|
||||
point_group
|
||||
|> PointGroup.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
||||
regimen_instances = list_regimen_instances()
|
||||
farm_events = list_farm_events()
|
||||
farm_events = Repo.all(FarmEvent)
|
||||
|
||||
# check for any matching asset using this point group.
|
||||
# This is pretty recursive and probably isn't super great
|
||||
|
@ -322,11 +371,11 @@ defmodule FarmbotCore.Asset do
|
|||
for asset <- farm_events ++ regimen_instances do
|
||||
# TODO(Connor) this might be worth creating a behaviour for
|
||||
if uses_point_group?(asset, point_group) do
|
||||
Logger.debug "#{inspect(asset)} uses PointGroup: #{inspect(point_group)}. Reindexing it."
|
||||
Logger.debug("#{inspect(asset)} uses PointGroup: #{inspect(point_group)}. Reindexing it.")
|
||||
FarmbotCore.AssetSupervisor.update_child(asset)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
updated
|
||||
end
|
||||
|
||||
|
@ -335,14 +384,16 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def uses_point_group?(%FarmEvent{body: body}, %PointGroup{id: point_group_id}) do
|
||||
|
||||
any_body_node_uses_point_group?(body, point_group_id)
|
||||
end
|
||||
|
||||
def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{id: point_group_id}) do
|
||||
any_body_node_uses_point_group?(body, point_group_id) || Enum.find(regimen_items, fn(%{sequence_id: sequence_id}) ->
|
||||
any_body_node_uses_point_group?(get_sequence(sequence_id).body, point_group_id)
|
||||
end)
|
||||
def uses_point_group?(%Regimen{body: body, regimen_items: regimen_items}, %PointGroup{
|
||||
id: point_group_id
|
||||
}) do
|
||||
any_body_node_uses_point_group?(body, point_group_id) ||
|
||||
Enum.find(regimen_items, fn %{sequence_id: sequence_id} ->
|
||||
any_body_node_uses_point_group?(get_sequence(sequence_id).body, point_group_id)
|
||||
end)
|
||||
end
|
||||
|
||||
def uses_point_group?(%RegimenInstance{farm_event: farm_event, regimen: regimen}, point_group) do
|
||||
|
@ -350,11 +401,13 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def any_body_node_uses_point_group?(body, point_group_id) do
|
||||
Enum.find(body, fn
|
||||
Enum.find(body, fn
|
||||
%{
|
||||
kind: "execute",
|
||||
body: execute_body
|
||||
} -> any_body_node_uses_point_group?(execute_body, point_group_id)
|
||||
} ->
|
||||
any_body_node_uses_point_group?(execute_body, point_group_id)
|
||||
|
||||
%{
|
||||
args: %{
|
||||
"data_value" => %{
|
||||
|
@ -364,7 +417,8 @@ defmodule FarmbotCore.Asset do
|
|||
"label" => "parent"
|
||||
},
|
||||
kind: "parameter_application"
|
||||
} -> true
|
||||
} ->
|
||||
true
|
||||
|
||||
%{
|
||||
args: %{
|
||||
|
@ -375,8 +429,11 @@ defmodule FarmbotCore.Asset do
|
|||
"label" => "parent"
|
||||
},
|
||||
kind: "parameter_application"
|
||||
} -> true
|
||||
_ -> false
|
||||
} ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -407,6 +464,7 @@ defmodule FarmbotCore.Asset do
|
|||
def new_public_key_from_home!() do
|
||||
public_key_path = Path.join([System.get_env("HOME"), ".ssh", "id_rsa.pub"])
|
||||
public_key = File.read!(public_key_path)
|
||||
|
||||
%PublicKey{}
|
||||
|> PublicKey.changeset(%{public_key: public_key})
|
||||
|> Repo.insert()
|
||||
|
@ -417,7 +475,7 @@ defmodule FarmbotCore.Asset do
|
|||
|> PublicKey.changeset(%{public_key: public_key})
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
|
||||
## End PublicKey
|
||||
|
||||
## Begin Regimen
|
||||
|
@ -435,18 +493,23 @@ defmodule FarmbotCore.Asset do
|
|||
end
|
||||
|
||||
def delete_regimen!(regimen) do
|
||||
regimen_instances = Repo.all(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id)
|
||||
regimen_instances =
|
||||
Repo.all(from(ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id))
|
||||
|
||||
for ri <- regimen_instances do
|
||||
IO.puts "deleting regimen instance: #{inspect(ri)}"
|
||||
IO.puts("deleting regimen instance: #{inspect(ri)}")
|
||||
delete_regimen_instance!(ri)
|
||||
end
|
||||
|
||||
Repo.delete!(regimen)
|
||||
end
|
||||
|
||||
@doc "Update an existing regimen"
|
||||
def update_regimen!(regimen, params) do
|
||||
regimen_instances = Repo.all(from ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id)
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
regimen_instances =
|
||||
Repo.all(from(ri in RegimenInstance, where: ri.regimen_id == ^regimen.local_id))
|
||||
|> Repo.preload([:farm_event, :regimen])
|
||||
|
||||
for ri <- regimen_instances do
|
||||
ri
|
||||
|> RegimenInstance.changeset(%{updated_at: DateTime.utc_now()})
|
||||
|
@ -469,23 +532,31 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def update_sequence!(%Sequence{} = sequence, params \\ %{}) do
|
||||
sequence_id = sequence.id
|
||||
farm_events = Repo.all(from f in FarmEvent,
|
||||
where: f.executable_type == "Sequence"
|
||||
and f.executable_id == ^sequence_id)
|
||||
|
||||
regimen_instances = RegimenInstance
|
||||
|> Repo.all()
|
||||
|> Repo.preload([:regimen, :farm_event])
|
||||
|> Enum.filter(fn
|
||||
%{regimen: %{regimen_items: items}} ->
|
||||
Enum.find(items, fn
|
||||
%{sequence_id: ^sequence_id} -> true
|
||||
%{sequence_id: _} -> true
|
||||
farm_events =
|
||||
Repo.all(
|
||||
from(f in FarmEvent,
|
||||
where:
|
||||
f.executable_type == "Sequence" and
|
||||
f.executable_id == ^sequence_id
|
||||
)
|
||||
)
|
||||
|
||||
regimen_instances =
|
||||
RegimenInstance
|
||||
|> Repo.all()
|
||||
|> Repo.preload([:regimen, :farm_event])
|
||||
|> Enum.filter(fn
|
||||
%{regimen: %{regimen_items: items}} ->
|
||||
Enum.find(items, fn
|
||||
%{sequence_id: ^sequence_id} -> true
|
||||
%{sequence_id: _} -> true
|
||||
end)
|
||||
|
||||
%{regimen: nil} ->
|
||||
false
|
||||
end)
|
||||
|
||||
%{regimen: nil} -> false
|
||||
end)
|
||||
|
||||
for asset <- farm_events ++ regimen_instances do
|
||||
FarmbotCore.AssetSupervisor.update_child(asset)
|
||||
end
|
||||
|
@ -507,10 +578,11 @@ defmodule FarmbotCore.Asset do
|
|||
def get_farmware_manifest(package) do
|
||||
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware, select: fwi.manifest))
|
||||
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation, select: fwi.manifest))
|
||||
|
||||
Enum.find(
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{package: pkg} -> pkg == package
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{package: pkg} -> pkg == package
|
||||
_ -> false
|
||||
end
|
||||
)
|
||||
|
@ -519,10 +591,11 @@ defmodule FarmbotCore.Asset do
|
|||
def get_farmware_installation(package) do
|
||||
first_party_farmwares = Repo.all(from(fwi in FirstPartyFarmware))
|
||||
regular_farmwares = Repo.all(from(fwi in FarmwareInstallation))
|
||||
|
||||
Enum.find(
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{manifest: %{package: pkg}} -> pkg == package
|
||||
first_party_farmwares ++ regular_farmwares,
|
||||
fn
|
||||
%{manifest: %{package: pkg}} -> pkg == package
|
||||
_ -> false
|
||||
end
|
||||
)
|
||||
|
@ -530,12 +603,14 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def upsert_farmware_manifest_by_id(id, params) do
|
||||
fwi = Repo.get_by(FarmwareInstallation, id: id) || %FarmwareInstallation{}
|
||||
|
||||
FarmwareInstallation.changeset(fwi, params)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
||||
def upsert_first_party_farmware_manifest_by_id(id, params) do
|
||||
fwi = Repo.get_by(FirstPartyFarmware, id: id) || %FirstPartyFarmware{}
|
||||
|
||||
FirstPartyFarmware.changeset(fwi, params)
|
||||
|> Repo.insert_or_update()
|
||||
end
|
||||
|
@ -557,12 +632,14 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
def new_farmware_env(params) do
|
||||
key = params["key"] || params[:key]
|
||||
fwe = with key when is_binary(key) <- key,
|
||||
[fwe | _] <- Repo.all(from fwe in FarmwareEnv, where: fwe.key == ^key) do
|
||||
fwe
|
||||
else
|
||||
_ -> %FarmwareEnv{}
|
||||
end
|
||||
|
||||
fwe =
|
||||
with key when is_binary(key) <- key,
|
||||
[fwe | _] <- Repo.all(from(fwe in FarmwareEnv, where: fwe.key == ^key)) do
|
||||
fwe
|
||||
else
|
||||
_ -> %FarmwareEnv{}
|
||||
end
|
||||
|
||||
FarmwareEnv.changeset(fwe, params)
|
||||
|> Repo.insert_or_update()
|
||||
|
@ -596,7 +673,7 @@ defmodule FarmbotCore.Asset do
|
|||
Sensor.changeset(%Sensor{}, params)
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
|
||||
def update_sensor!(sensor, params) do
|
||||
sensor
|
||||
|> Sensor.changeset(params)
|
||||
|
@ -624,15 +701,6 @@ defmodule FarmbotCore.Asset do
|
|||
|
||||
## End SensorReading
|
||||
|
||||
## Begin DiagnosticDump
|
||||
|
||||
def new_diagnostic_dump(params) do
|
||||
DiagnosticDump.changeset(%DiagnosticDump{}, params)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
## End DiagnosticDump
|
||||
|
||||
## Begin DeviceCert
|
||||
|
||||
def new_device_cert(params) do
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule FarmbotCore.Asset.Command do
|
|||
"""
|
||||
require Logger
|
||||
alias FarmbotCore.{Asset, Asset.Repo}
|
||||
|
||||
alias FarmbotCore.Asset.{
|
||||
Device,
|
||||
FarmEvent,
|
||||
|
@ -44,31 +45,31 @@ defmodule FarmbotCore.Asset.Command do
|
|||
:ok
|
||||
end
|
||||
|
||||
def update(Device, _id, params) do
|
||||
def update(Device, _id, params) do
|
||||
Asset.update_device!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FbosConfig, id, nil) do
|
||||
def update(FbosConfig, id, nil) do
|
||||
Asset.delete_fbos_config!(id)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FbosConfig, _id, params) do
|
||||
def update(FbosConfig, _id, params) do
|
||||
Asset.update_fbos_config!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FirmwareConfig, id, nil) do
|
||||
def update(FirmwareConfig, id, nil) do
|
||||
Asset.delete_firmware_config!(id)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FirmwareConfig, _id, params) do
|
||||
|
||||
def update(FirmwareConfig, _id, params) do
|
||||
Asset.update_firmware_config!(params)
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
# Deletion use case:
|
||||
# TODO(Connor) put checks for deleting Device, FbosConfig and FirmwareConfig
|
||||
|
||||
|
@ -96,12 +97,12 @@ defmodule FarmbotCore.Asset.Command do
|
|||
:ok
|
||||
end
|
||||
|
||||
def update(FarmwareEnv, id, params) do
|
||||
def update(FarmwareEnv, id, params) do
|
||||
Asset.upsert_farmware_env_by_id(id, params)
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(FarmwareInstallation, id, params) do
|
||||
|
||||
def update(FarmwareInstallation, id, params) do
|
||||
Asset.upsert_farmware_manifest_by_id(id, params)
|
||||
:ok
|
||||
end
|
||||
|
@ -112,15 +113,17 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(FarmEvent, id, params) do
|
||||
old = Asset.get_farm_event(id)
|
||||
if old,
|
||||
do: Asset.update_farm_event!(old, params),
|
||||
|
||||
if old,
|
||||
do: Asset.update_farm_event!(old, params),
|
||||
else: Asset.new_farm_event!(params)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(PublicKey, id, params) do
|
||||
old = Asset.get_public_key(id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_public_key!(old, params),
|
||||
else: Asset.new_public_key!(params)
|
||||
|
@ -130,33 +133,37 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(Regimen, id, params) do
|
||||
old = Asset.get_regimen(id)
|
||||
if old,
|
||||
do: Asset.update_regimen!(old, params),
|
||||
|
||||
if old,
|
||||
do: Asset.update_regimen!(old, params),
|
||||
else: Asset.new_regimen!(params)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(Sensor, id, params) do
|
||||
old = Asset.get_sensor(id)
|
||||
if old,
|
||||
do: Asset.update_sensor!(old, params),
|
||||
|
||||
if old,
|
||||
do: Asset.update_sensor!(old, params),
|
||||
else: Asset.new_sensor!(params)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(SensorReading, id, params) do
|
||||
old = Asset.get_sensor_reading(id)
|
||||
if old,
|
||||
do: Asset.update_sensor_reading!(old, params),
|
||||
|
||||
if old,
|
||||
do: Asset.update_sensor_reading!(old, params),
|
||||
else: Asset.new_sensor_reading!(params)
|
||||
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def update(Sequence, id, params) do
|
||||
old = Asset.get_sequence(id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_sequence!(old, params),
|
||||
else: Asset.new_sequence!(params)
|
||||
|
@ -172,6 +179,7 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
def update(PointGroup, id, params) do
|
||||
old = Asset.get_point_group(id: id)
|
||||
|
||||
if old,
|
||||
do: Asset.update_point_group!(old, params),
|
||||
else: Asset.new_point_group!(params)
|
||||
|
@ -181,8 +189,8 @@ defmodule FarmbotCore.Asset.Command do
|
|||
|
||||
# Catch-all use case:
|
||||
def update(asset_kind, id, params) do
|
||||
Logger.warn "AssetCommand needs implementation: #{asset_kind}"
|
||||
mod = as_module!(asset_kind)
|
||||
|
||||
case Repo.get_by(mod, id: id) do
|
||||
nil ->
|
||||
struct!(mod)
|
||||
|
@ -205,28 +213,27 @@ defmodule FarmbotCore.Asset.Command do
|
|||
mod.changeset(asset, params)
|
||||
end
|
||||
|
||||
defp as_module!("Device"), do: Asset.Device
|
||||
defp as_module!("DiagnosticDump"), do: Asset.DiagnosticDump
|
||||
defp as_module!("FarmEvent"), do: Asset.FarmEvent
|
||||
defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv
|
||||
defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware
|
||||
defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation
|
||||
defp as_module!("FbosConfig"), do: Asset.FbosConfig
|
||||
defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig
|
||||
defp as_module!("Peripheral"), do: Asset.Peripheral
|
||||
defp as_module!("PinBinding"), do: Asset.PinBinding
|
||||
defp as_module!("Point"), do: Asset.Point
|
||||
defp as_module!("PointGroup"), do: Asset.PointGroup
|
||||
defp as_module!("Regimen"), do: Asset.Regimen
|
||||
defp as_module!("Sensor"), do: Asset.Sensor
|
||||
defp as_module!("SensorReading"), do: Asset.SensorReading
|
||||
defp as_module!("Sequence"), do: Asset.Sequence
|
||||
defp as_module!("Tool"), do: Asset.Tool
|
||||
defp as_module!("Device"), do: Asset.Device
|
||||
defp as_module!("FarmEvent"), do: Asset.FarmEvent
|
||||
defp as_module!("FarmwareEnv"), do: Asset.FarmwareEnv
|
||||
defp as_module!("FirstPartyFarmware"), do: Asset.FirstPartyFarmware
|
||||
defp as_module!("FarmwareInstallation"), do: Asset.FarmwareInstallation
|
||||
defp as_module!("FbosConfig"), do: Asset.FbosConfig
|
||||
defp as_module!("FirmwareConfig"), do: Asset.FirmwareConfig
|
||||
defp as_module!("Peripheral"), do: Asset.Peripheral
|
||||
defp as_module!("PinBinding"), do: Asset.PinBinding
|
||||
defp as_module!("Point"), do: Asset.Point
|
||||
defp as_module!("PointGroup"), do: Asset.PointGroup
|
||||
defp as_module!("Regimen"), do: Asset.Regimen
|
||||
defp as_module!("Sensor"), do: Asset.Sensor
|
||||
defp as_module!("SensorReading"), do: Asset.SensorReading
|
||||
defp as_module!("Sequence"), do: Asset.Sequence
|
||||
defp as_module!("Tool"), do: Asset.Tool
|
||||
|
||||
defp as_module!(module) when is_atom(module) do
|
||||
as_module!(List.last(Module.split(module)))
|
||||
end
|
||||
|
||||
|
||||
defp as_module!(kind) when is_binary(kind) do
|
||||
raise("""
|
||||
Unknown kind: #{kind}
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||
alias FarmbotCore.Asset.{ PointGroup, Repo, Point }
|
||||
import Ecto.Query
|
||||
|
||||
@moduledoc """
|
||||
The PointGroup asset declares a list
|
||||
of criteria to query points. This
|
||||
module then converts that criteria to
|
||||
a list of real points that match the
|
||||
criteria of a point group.
|
||||
|
||||
Example:
|
||||
|
||||
You have a PointGroup with a criteria
|
||||
where group.criteria.number_gt.x == 10
|
||||
Passing that PointGroup to this module
|
||||
will return an array of `Point` assets
|
||||
with an x property that is greater than
|
||||
10.
|
||||
"""
|
||||
|
||||
# We will not query any string/numeric fields other than these.
|
||||
# Updating the PointGroup / Point models may require an update
|
||||
# to these fields.
|
||||
@numberic_fields ["radius", "x", "y", "z", "pullout_direction"]
|
||||
@string_fields ["name", "openfarm_slug", "plant_stage", "pointer_type"]
|
||||
|
||||
@doc """
|
||||
You provide it a %PointGroup{},
|
||||
it provides you an array of
|
||||
point records (%Point{}) that match
|
||||
the group's criteria.
|
||||
"""
|
||||
def run(%PointGroup{point_ids: static_ids} = pg) do
|
||||
# pg.point_ids is *always* include in search results,
|
||||
# even if it does not match pg.criteria in any way.
|
||||
always_ok = Repo.all(from(p in Point, where: p.id in ^static_ids, select: p))
|
||||
# Now we need a list of point IDs that actually match
|
||||
# the pg.criteria fields. We only get the ID because
|
||||
# we are circumventing Ecto and doing raw SQL.
|
||||
# There may be better ways to do this:
|
||||
dynamic_ids = find_matching_point_ids(pg)
|
||||
|
||||
# Once we have a list of matching criteria, we can run a
|
||||
# SQL query through Ecto to return real %Point{} structs...
|
||||
dynamic_query = from(p in Point, where: p.id in ^dynamic_ids, select: p)
|
||||
# ...but we're not done! If the criteria contains meta fields,
|
||||
# we need to perform a lookup in memory
|
||||
needs_meta_filter = Repo.all(dynamic_query)
|
||||
# There we go. We have all the matching %Point{}s
|
||||
search_matches = search_meta_fields(pg, needs_meta_filter)
|
||||
# ...but there are duplicates. We can remove them via uniq_by:
|
||||
Enum.uniq_by((search_matches ++ always_ok), fn p -> p.id end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Takes intermediate search results and makes them
|
||||
more specific by iterating over the search results
|
||||
and only returning the ones that match the meta.*
|
||||
field provided in pg.criteria
|
||||
"""
|
||||
def search_meta_fields(%PointGroup{} = pg, points) do
|
||||
meta = "meta."
|
||||
meta_len = String.length(meta)
|
||||
|
||||
(pg.criteria["string_eq"] || %{})
|
||||
|> Map.to_list()
|
||||
|> Enum.filter(fn {k, _v} ->
|
||||
String.starts_with?(k, meta)
|
||||
end)
|
||||
|> Enum.map(fn {k, value} ->
|
||||
clean_key = String.slice(k, ((meta_len)..-1))
|
||||
{clean_key, value}
|
||||
end)
|
||||
|> Enum.reduce(%{}, fn {key, value}, all ->
|
||||
all_values = all[key] || []
|
||||
Map.merge(all, %{key => value ++ all_values})
|
||||
end)
|
||||
|> Map.to_list()
|
||||
|> Enum.reduce(points, fn {key, values}, finalists ->
|
||||
finalists
|
||||
|> Enum.filter(fn point -> point.meta[key] end)
|
||||
|> Enum.filter(fn point -> point.meta[key] in values end)
|
||||
end)
|
||||
end
|
||||
|
||||
# Find all point IDs that are matched by a PointGroup
|
||||
# This is not including any meta.* search terms,
|
||||
# since `meta` is a JSON column that must be
|
||||
# manually searched in memory.
|
||||
defp find_matching_point_ids(%PointGroup{} = pg) do
|
||||
results = {pg, []}
|
||||
|> stage_1("string_eq", @string_fields, "IN")
|
||||
|> stage_1("number_eq", @numberic_fields, "IN")
|
||||
|> stage_1("number_gt", @numberic_fields, ">")
|
||||
|> stage_1("number_lt", @numberic_fields, "<")
|
||||
|> stage_1_day_field()
|
||||
|> unwrap_stage_1()
|
||||
|> Enum.reduce(%{}, &stage_2/2)
|
||||
|> Map.to_list()
|
||||
|> Enum.reduce({[], [], 0}, &stage_3/2)
|
||||
|> unwrap_stage_3()
|
||||
|> finalize()
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
# EDGE CASE: If the user _only_ wants to put static points
|
||||
# in their group, we need to perform 0 SQL queries.
|
||||
def finalize({_, []}) do
|
||||
[]
|
||||
end
|
||||
|
||||
def finalize({fragments, criteria}) do
|
||||
x = Enum.join(fragments, " AND ")
|
||||
sql = "SELECT id FROM points WHERE #{x}"
|
||||
query_params = List.flatten(criteria)
|
||||
{:ok, query} = Repo.query(sql, query_params)
|
||||
%Sqlite.DbConnection.Result{ rows: rows } = query
|
||||
List.flatten(rows)
|
||||
end
|
||||
|
||||
defp unwrap_stage_1({_, right}), do: right
|
||||
defp unwrap_stage_3({query, args, _count}), do: {query, args}
|
||||
|
||||
defp stage_1({pg, accum}, kind, fields, op) do
|
||||
results = fields
|
||||
|> Enum.map(fn field -> {field, pg.criteria[kind][field]} end)
|
||||
|> Enum.filter(fn {_k, v} -> v end)
|
||||
|> Enum.map(fn {k, v} -> {k, op, v} end)
|
||||
{pg, accum ++ results}
|
||||
end
|
||||
|
||||
defp stage_1_day_field({pg, accum}) do
|
||||
day_criteria = pg.criteria["day"] || pg.criteria[:day] || %{}
|
||||
days = day_criteria["days_ago"] || day_criteria[:days_ago] || 0
|
||||
op = day_criteria["op"] || day_criteria[:op] || "<"
|
||||
time = Timex.shift(Timex.now(), days: -1 * days)
|
||||
|
||||
if days == 0 do
|
||||
{ pg, accum }
|
||||
else
|
||||
inverted_op = if op == ">" do "<" else ">" end
|
||||
|
||||
{ pg, accum ++ [{"created_at", inverted_op, time}] }
|
||||
end
|
||||
end
|
||||
|
||||
defp stage_2({lhs, "IN", rhs}, results) do
|
||||
query = "#{lhs} IN"
|
||||
all_values = results[query] || []
|
||||
Map.merge(results, %{query => rhs ++ all_values})
|
||||
end
|
||||
|
||||
defp stage_2({lhs, op, rhs}, results) do
|
||||
Map.merge(results, %{"#{lhs} #{op}" => rhs})
|
||||
end
|
||||
|
||||
# Interpolatinng data turned out to be hard.
|
||||
# Pretty sure there is an easier way to do this.
|
||||
# NOT OK: Repo.query("SELECT foo WHERE bar IN $0", [[1, 2, 3]])
|
||||
# OK: Repo.query("SELECT foo WHERE bar IN ($0, $1, $2)", [1, 2, 3])
|
||||
defp stage_3({sql, args}, {full_query, full_args, count0}) when is_list(args) do
|
||||
arg_count = Enum.count(args)
|
||||
final = count0 + (arg_count - 1)
|
||||
initial_state = {sql, count0}
|
||||
{next_sql, _} =
|
||||
if arg_count == 1 do
|
||||
{sql <> " ($#{count0})", nil}
|
||||
else
|
||||
Enum.reduce(args, initial_state, fn
|
||||
(_, {sql, ^count0}) -> {sql <> " ($#{count0},", count0+1}
|
||||
(_, {sql, ^final}) -> {sql <> " $#{final})", final}
|
||||
(_, {sql, count}) -> {sql <> " $#{count},", count+1}
|
||||
end)
|
||||
end
|
||||
{full_query ++ [next_sql], full_args ++ [args], final + 1}
|
||||
end
|
||||
|
||||
defp stage_3({sql, args}, {full_query, full_args, count}) do
|
||||
{full_query ++ [sql <> " $#{count}"], full_args ++ [args], count + 1}
|
||||
end
|
||||
end
|
|
@ -1,58 +0,0 @@
|
|||
defmodule FarmbotCore.Asset.DiagnosticDump do
|
||||
@moduledoc """
|
||||
Just the DiagDump REST resource, used by FarmBot staff to help users debug
|
||||
remote device problems.
|
||||
"""
|
||||
use FarmbotCore.Asset.Schema, path: "/api/diagnostic_dumps"
|
||||
|
||||
schema "diagnostic_dumps" do
|
||||
field(:id, :id)
|
||||
|
||||
has_one(:local_meta, FarmbotCore.Asset.Private.LocalMeta,
|
||||
on_delete: :delete_all,
|
||||
references: :local_id,
|
||||
foreign_key: :asset_local_id
|
||||
)
|
||||
|
||||
field(:ticket_identifier, :string)
|
||||
field(:fbos_commit, :string)
|
||||
field(:fbos_version, :string)
|
||||
field(:firmware_commit, :string)
|
||||
field(:firmware_state, :string)
|
||||
field(:network_interface, :string)
|
||||
field(:fbos_dmesg_dump, :string)
|
||||
field(:monitor, :boolean, default: true)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
view diagnostic_dump do
|
||||
%{
|
||||
id: diagnostic_dump.id,
|
||||
ticket_identifier: diagnostic_dump.ticket_identifier,
|
||||
fbos_commit: diagnostic_dump.fbos_commit,
|
||||
fbos_version: diagnostic_dump.fbos_version,
|
||||
firmware_commit: diagnostic_dump.firmware_commit,
|
||||
firmware_state: diagnostic_dump.firmware_state,
|
||||
network_interface: diagnostic_dump.network_interface,
|
||||
fbos_dmesg_dump: diagnostic_dump.fbos_dmesg_dump
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(diagnostic_dump, params \\ %{}) do
|
||||
diagnostic_dump
|
||||
|> cast(params, [
|
||||
:id,
|
||||
:ticket_identifier,
|
||||
:fbos_commit,
|
||||
:fbos_version,
|
||||
:firmware_commit,
|
||||
:firmware_state,
|
||||
:network_interface,
|
||||
:fbos_dmesg_dump,
|
||||
:monitor,
|
||||
:created_at,
|
||||
:updated_at
|
||||
])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
|
@ -100,16 +100,15 @@ defimpl String.Chars, for: FarmbotCore.Asset.PinBinding do
|
|||
end
|
||||
|
||||
defp special_action(button_number, action, pin_num) do
|
||||
"Button #{button_number}: #{format_action(action)} (Pi #{pin_num})"
|
||||
"Button #{button_number}: #{format_action(action)} (Pi #{pin_num})"
|
||||
end
|
||||
|
||||
defp format_action("dump_info"), do: "Dump Info"
|
||||
defp format_action("emergency_lock"), do: "E-Stop"
|
||||
defp format_action("emergency_lock"), do: "E-Stop"
|
||||
defp format_action("emergency_unlock"), do: "E-Unlock"
|
||||
defp format_action("power_off"), do: "Power Off"
|
||||
defp format_action("read_status"), do: "Read Status"
|
||||
defp format_action("reboot"), do: "Reboot"
|
||||
defp format_action("sync"), do: "Sync"
|
||||
defp format_action("take_photo"), do: "Take Photo"
|
||||
defp format_action("take_photo"), do: "Take Photo"
|
||||
defp format_action(_), do: nil
|
||||
end
|
||||
|
|
|
@ -13,18 +13,21 @@ defmodule FarmbotCore.Asset.Point do
|
|||
foreign_key: :asset_local_id
|
||||
)
|
||||
|
||||
field(:discarded_at, :utc_datetime)
|
||||
field(:gantry_mounted, :boolean)
|
||||
field(:meta, :map)
|
||||
field(:monitor, :boolean, default: true)
|
||||
field(:name, :string)
|
||||
field(:openfarm_slug, :string)
|
||||
field(:plant_stage, :string)
|
||||
field(:planted_at, :utc_datetime)
|
||||
field(:pointer_type, :string)
|
||||
field(:pullout_direction, :integer)
|
||||
field(:radius, :float)
|
||||
field(:tool_id, :integer)
|
||||
field(:x, :float)
|
||||
field(:y, :float)
|
||||
field(:z, :float)
|
||||
field(:tool_id, :integer)
|
||||
field(:discarded_at, :utc_datetime)
|
||||
field(:monitor, :boolean, default: true)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
|
@ -34,11 +37,15 @@ defmodule FarmbotCore.Asset.Point do
|
|||
meta: point.meta,
|
||||
name: point.name,
|
||||
plant_stage: point.plant_stage,
|
||||
created_at: point.created_at,
|
||||
planted_at: point.planted_at,
|
||||
pointer_type: point.pointer_type,
|
||||
radius: point.radius,
|
||||
tool_id: point.tool_id,
|
||||
discarded_at: point.discarded_at,
|
||||
gantry_mounted: point.gantry_mounted,
|
||||
openfarm_slug: point.openfarm_slug,
|
||||
pullout_direction: point.pullout_direction,
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: point.z
|
||||
|
@ -48,21 +55,24 @@ defmodule FarmbotCore.Asset.Point do
|
|||
def changeset(point, params \\ %{}) do
|
||||
point
|
||||
|> cast(params, [
|
||||
:created_at,
|
||||
:discarded_at,
|
||||
:gantry_mounted,
|
||||
:id,
|
||||
:meta,
|
||||
:monitor,
|
||||
:name,
|
||||
:plant_stage,
|
||||
:planted_at,
|
||||
:pointer_type,
|
||||
:pullout_direction,
|
||||
:openfarm_slug,
|
||||
:radius,
|
||||
:tool_id,
|
||||
:updated_at,
|
||||
:x,
|
||||
:y,
|
||||
:z,
|
||||
:tool_id,
|
||||
:discarded_at,
|
||||
:monitor,
|
||||
:created_at,
|
||||
:updated_at
|
||||
])
|
||||
|> validate_required([])
|
||||
end
|
||||
|
|
|
@ -5,6 +5,18 @@ defmodule FarmbotCore.Asset.PointGroup do
|
|||
|
||||
use FarmbotCore.Asset.Schema, path: "/api/point_groups"
|
||||
|
||||
@default_criteria %{
|
||||
"day" => %{ "op" => ">", "days_ago" => 0 },
|
||||
# Map<string, string[] | undefined>,
|
||||
"string_eq" => %{},
|
||||
# Map<string, number[] | undefined>,
|
||||
"number_eq" => %{},
|
||||
# Map<string, number | undefined>,
|
||||
"number_lt" => %{},
|
||||
# Map<string, number | undefined>,
|
||||
"number_gt" => %{},
|
||||
}
|
||||
|
||||
schema "point_groups" do
|
||||
field(:id, :id)
|
||||
|
||||
|
@ -17,6 +29,7 @@ defmodule FarmbotCore.Asset.PointGroup do
|
|||
field(:name, :string)
|
||||
field(:point_ids, {:array, :integer})
|
||||
field(:sort_type, :string)
|
||||
field(:criteria, :map, default: @default_criteria)
|
||||
|
||||
field(:monitor, :boolean, default: true)
|
||||
timestamps()
|
||||
|
@ -27,13 +40,23 @@ defmodule FarmbotCore.Asset.PointGroup do
|
|||
id: point_group.id,
|
||||
name: point_group.name,
|
||||
point_ids: point_group.point_ids,
|
||||
sort_type: point_group.sort_type
|
||||
sort_type: point_group.sort_type,
|
||||
criteria: point_group.criteria
|
||||
}
|
||||
end
|
||||
|
||||
def changeset(point_group, params \\ %{}) do
|
||||
point_group
|
||||
|> cast(params, [:id, :name, :point_ids, :sort_type, :monitor, :created_at, :updated_at])
|
||||
|> cast(params, [
|
||||
:id,
|
||||
:name,
|
||||
:criteria,
|
||||
:point_ids,
|
||||
:sort_type,
|
||||
:monitor,
|
||||
:created_at,
|
||||
:updated_at
|
||||
])
|
||||
|> validate_required([])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,17 +40,17 @@ defmodule FarmbotCore.Asset.Private do
|
|||
# Because sqlite can't test unique constraints before a transaction, if this function gets called for
|
||||
# the same asset more than once asyncronously, the asset can be marked dirty twice at the same time
|
||||
# causing the `unique constraint` error to happen in either `ecto` OR `sqlite`. I've
|
||||
# caught both errors here as they are both essentially the same thing, and can be safely
|
||||
# caught both errors here as they are both essentially the same thing, and can be safely
|
||||
# discarded. Doing an `insert_or_update/1` (without the bang) can still result in the sqlite
|
||||
# error being thrown.
|
||||
# error being thrown.
|
||||
changeset = LocalMeta.changeset(local_meta, Map.merge(params, %{table: table, status: "dirty"}))
|
||||
try do
|
||||
Repo.insert_or_update!(changeset)
|
||||
catch
|
||||
:error, %Sqlite.DbConnection.Error{
|
||||
message: "UNIQUE constraint failed: local_metas.table, local_metas.asset_local_id",
|
||||
message: "UNIQUE constraint failed: local_metas.table, local_metas.asset_local_id",
|
||||
sqlite: %{code: :constraint}
|
||||
} ->
|
||||
} ->
|
||||
Logger.warn """
|
||||
Caught race condition marking data as dirty (sqlite)
|
||||
table: #{inspect(table)}
|
||||
|
@ -59,10 +59,10 @@ defmodule FarmbotCore.Asset.Private do
|
|||
Ecto.Changeset.apply_changes(changeset)
|
||||
:error, %Ecto.InvalidChangesetError{
|
||||
changeset: %{
|
||||
action: :insert,
|
||||
action: :insert,
|
||||
errors: [
|
||||
table: {"LocalMeta already exists.", [
|
||||
validation: :unsafe_unique,
|
||||
validation: :unsafe_unique,
|
||||
fields: [:table, :asset_local_id]
|
||||
]}
|
||||
]}
|
||||
|
@ -73,7 +73,7 @@ defmodule FarmbotCore.Asset.Private do
|
|||
id: #{inspect(asset.local_id)}
|
||||
"""
|
||||
Ecto.Changeset.apply_changes(changeset)
|
||||
type, reason ->
|
||||
type, reason ->
|
||||
FarmbotCore.Logger.error 1, """
|
||||
Caught unexpected error marking data as dirty
|
||||
table: #{inspect(table)}
|
||||
|
|
|
@ -9,7 +9,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
alias FarmbotCore.Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
DiagnosticDump,
|
||||
DeviceCert,
|
||||
FarmEvent,
|
||||
FarmwareEnv,
|
||||
|
@ -47,13 +46,6 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
define_field: false
|
||||
)
|
||||
|
||||
belongs_to(:diagnostic_dump, DiagnosticDump,
|
||||
foreign_key: :asset_local_id,
|
||||
type: :binary_id,
|
||||
references: :local_id,
|
||||
define_field: false
|
||||
)
|
||||
|
||||
belongs_to(:farm_event, FarmEvent,
|
||||
foreign_key: :asset_local_id,
|
||||
type: :binary_id,
|
||||
|
@ -174,8 +166,7 @@ defmodule FarmbotCore.Asset.Private.LocalMeta do
|
|||
"firmware_configs",
|
||||
"fbos_configs",
|
||||
"farmware_installations",
|
||||
"farmware_envs",
|
||||
"diagnostic_dumps"
|
||||
"farmware_envs"
|
||||
])
|
||||
|> unsafe_validate_unique([:table, :asset_local_id], Repo,
|
||||
message: "LocalMeta already exists."
|
||||
|
|
|
@ -23,7 +23,11 @@ defmodule FarmbotCore.Asset.Supervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
Supervisor.init(children(), strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def children do
|
||||
default = [
|
||||
Repo,
|
||||
{AssetSupervisor, module: FbosConfig},
|
||||
{AssetSupervisor, module: FirmwareConfig},
|
||||
|
@ -38,7 +42,7 @@ defmodule FarmbotCore.Asset.Supervisor do
|
|||
{AssetSupervisor, module: FarmwareEnv},
|
||||
AssetMonitor,
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,6 @@ defmodule FarmbotCore.Asset.Sync do
|
|||
embeds_many(:devices, Item)
|
||||
embeds_many(:firmware_configs, Item)
|
||||
embeds_many(:fbos_configs, Item)
|
||||
embeds_many(:diagnostic_dumps, Item)
|
||||
embeds_many(:farm_events, Item)
|
||||
embeds_many(:farmware_envs, Item)
|
||||
embeds_many(:first_party_farmwares, Item)
|
||||
|
@ -65,7 +64,6 @@ defmodule FarmbotCore.Asset.Sync do
|
|||
devices: Enum.map(sync.device, &Item.render/1),
|
||||
fbos_configs: Enum.map(sync.fbos_config, &Item.render/1),
|
||||
firmware_configs: Enum.map(sync.firmware_config, &Item.render/1),
|
||||
diagnostic_dumps: Enum.map(sync.diagnostic_dumps, &Item.render/1),
|
||||
farm_events: Enum.map(sync.farm_events, &Item.render/1),
|
||||
farmware_envs: Enum.map(sync.farmware_envs, &Item.render/1),
|
||||
first_party_farmwares: Enum.map(sync.first_party_farmwares, &Item.render/1),
|
||||
|
@ -90,7 +88,6 @@ defmodule FarmbotCore.Asset.Sync do
|
|||
|> cast_embed(:devices)
|
||||
|> cast_embed(:fbos_configs)
|
||||
|> cast_embed(:firmware_configs)
|
||||
|> cast_embed(:diagnostic_dumps)
|
||||
|> cast_embed(:farm_events)
|
||||
|> cast_embed(:farmware_envs)
|
||||
|> cast_embed(:farmware_installations)
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
defmodule FarmbotCore.AssetHelpers do
|
||||
@moduledoc """
|
||||
Helpers for the console at runtime.
|
||||
|
||||
|
||||
Example:
|
||||
iex()> use FarmbotCore.AssetHelpers
|
||||
iex()> Repo.all(Device)
|
||||
[%Device{}]
|
||||
"""
|
||||
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_opts) do
|
||||
require Logger
|
||||
Logger.warn "Don't use this in production please!"
|
||||
Logger.warn("Don't use this in production please!")
|
||||
|
||||
quote do
|
||||
import Ecto.Query
|
||||
alias FarmbotCore.Asset
|
||||
|
||||
alias Asset.{
|
||||
Repo,
|
||||
Device,
|
||||
DeviceCert,
|
||||
DiagnosticDump,
|
||||
FarmwareEnv,
|
||||
FarmwareInstallation,
|
||||
FirstPartyFarmware,
|
||||
|
@ -40,4 +41,4 @@ defmodule FarmbotCore.AssetHelpers do
|
|||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,7 +82,6 @@ defmodule FarmbotCore.AssetMonitor do
|
|||
sub_state = Map.drop(sub_state, deleted_ids)
|
||||
|
||||
Enum.each(deleted_ids, fn local_id ->
|
||||
Logger.error("#{inspect(kind)} #{local_id} needs to be terminated")
|
||||
AssetSupervisor.terminate_child(kind, local_id)
|
||||
end)
|
||||
|
||||
|
@ -94,13 +93,11 @@ defmodule FarmbotCore.AssetMonitor do
|
|||
Map.put(sub_state, id, updated_at)
|
||||
|
||||
is_nil(sub_state[id]) ->
|
||||
Logger.debug("#{inspect(kind)} #{id} needs to be started")
|
||||
asset = Repo.preload(asset, AssetWorker.preload(asset))
|
||||
:ok = AssetSupervisor.start_child(asset) |> assert_result!(asset)
|
||||
Map.put(sub_state, id, updated_at)
|
||||
|
||||
compare_datetimes(updated_at, sub_state[id]) == :gt ->
|
||||
Logger.warn("#{inspect(kind)} #{id} needs to be updated")
|
||||
asset = Repo.preload(asset, AssetWorker.preload(asset))
|
||||
:ok = AssetSupervisor.update_child(asset) |> assert_result!(asset)
|
||||
Map.put(sub_state, id, updated_at)
|
||||
|
|
|
@ -66,9 +66,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Device do
|
|||
{:mounted_tool_id, nil} ->
|
||||
if old_device.mounted_tool_id do
|
||||
if tool = Asset.get_tool(id: old_device.mounted_tool_id) do
|
||||
FarmbotCore.Logger.info(2, "Farmbot dismounted #{tool.name}")
|
||||
FarmbotCore.Logger.info(2, "Dismounted the #{tool.name}")
|
||||
else
|
||||
FarmbotCore.Logger.info(2, "Farmbot dismounted unknown tool")
|
||||
FarmbotCore.Logger.info(2, "Dismounted unknown tool")
|
||||
end
|
||||
else
|
||||
# no previously mounted tool
|
||||
|
@ -77,9 +77,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Device do
|
|||
|
||||
{:mounted_tool_id, id} ->
|
||||
if tool = Asset.get_tool(id: id) do
|
||||
FarmbotCore.Logger.info(2, "Farmbot mounted #{tool.name}")
|
||||
FarmbotCore.Logger.info(2, "Mounted the #{tool.name}")
|
||||
else
|
||||
FarmbotCore.Logger.info(2, "Farmbot mounted unknown tool")
|
||||
FarmbotCore.Logger.info(2, "Mounted unknown tool")
|
||||
end
|
||||
|
||||
{_key, _value} ->
|
||||
|
|
|
@ -11,16 +11,9 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.FbosConfig do
|
|||
alias FarmbotCore.{Asset.FbosConfig, BotState, Config}
|
||||
import FarmbotFirmware.PackageUtils, only: [package_to_string: 1]
|
||||
|
||||
@firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold]
|
||||
@firmware_flash_attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_attempt_threshold] || 5
|
||||
@firmware_flash_timeout Application.get_env(:farmbot_core, __MODULE__)[:firmware_flash_timeout] || 5000
|
||||
@disable_firmware_io_logs_timeout Application.get_env(:farmbot_core, __MODULE__)[:disable_firmware_io_logs_timeout] || 300000
|
||||
@firmware_flash_attempt_threshold || Mix.raise """
|
||||
Firmware open attempt threshold not configured:
|
||||
|
||||
config :farmbot_core, #{__MODULE__}, [
|
||||
firmware_flash_attempt_threshold: :infinity
|
||||
]
|
||||
"""
|
||||
|
||||
@impl FarmbotCore.AssetWorker
|
||||
def preload(%FbosConfig{}), do: []
|
||||
|
|
|
@ -33,7 +33,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info(:timeout, %{fw_version: "8.0.0.S"} = state) do
|
||||
def handle_info(:timeout, %{fw_version: "8.0.0.S.stub"} = state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
@ -47,15 +47,15 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.Peripheral do
|
|||
Logger.debug("Read peripheral: #{peripheral.label}")
|
||||
rpc = peripheral_to_rpc(peripheral)
|
||||
case FarmbotCeleryScript.execute(rpc, make_ref()) do
|
||||
:ok ->
|
||||
:ok ->
|
||||
Logger.debug("Read peripheral: #{peripheral.label} ok")
|
||||
{:noreply, state}
|
||||
|
||||
{:error, reason} when errors < 5 ->
|
||||
|
||||
{:error, reason} when errors < 5 ->
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=#{state.errors}")
|
||||
Process.send_after(self(), :timeout, @retry_ms)
|
||||
{:noreply, %{state | errors: state.errors + 1}}
|
||||
|
||||
|
||||
{:error, reason} when errors == 5 ->
|
||||
Logger.error("Read peripheral: #{peripheral.label} error: #{reason} errors=5 not trying again.")
|
||||
{:noreply, state}
|
||||
|
|
|
@ -3,12 +3,12 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
Worker for monitoring hardware GPIO. (not related to the mcu firmware.)
|
||||
|
||||
Upon a button trigger, a `sequence`, or `special_action` will be executed by
|
||||
the CeleryScript Runtime.
|
||||
the CeleryScript Runtime.
|
||||
|
||||
This module also defines a behaviour that allows for abstracting and testing
|
||||
This module also defines a behaviour that allows for abstracting and testing
|
||||
independent of GPIO hardware code.
|
||||
"""
|
||||
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
require FarmbotCore.Logger
|
||||
|
@ -75,6 +75,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
case Asset.get_sequence(pin_binding.sequence_id) do
|
||||
%Sequence{name: name} = seq ->
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing #{name}")
|
||||
|
||||
AST.decode(seq)
|
||||
|> execute(state)
|
||||
|
||||
|
@ -84,24 +85,24 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "dump_info"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Dump Info")
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.dump_info()
|
||||
|> execute(state)
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state) do
|
||||
def handle_cast(
|
||||
:trigger,
|
||||
%{pin_binding: %{special_action: "emergency_lock"} = pin_binding} = state
|
||||
) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Lock")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.emergency_lock()
|
||||
|> execute(state)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "emergency_unlock"} = pin_binding} = state) do
|
||||
def handle_cast(
|
||||
:trigger,
|
||||
%{pin_binding: %{special_action: "emergency_unlock"} = pin_binding} = state
|
||||
) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Emergency Unlock")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.emergency_unlock()
|
||||
|
@ -110,14 +111,19 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "power_off"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Power Off")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.power_off()
|
||||
|> execute(state)
|
||||
end
|
||||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "read_status"} = pin_binding} = state) do
|
||||
def handle_cast(
|
||||
:trigger,
|
||||
%{pin_binding: %{special_action: "read_status"} = pin_binding} = state
|
||||
) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Read Status")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.read_status()
|
||||
|
@ -126,6 +132,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "reboot"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Reboot")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.reboot()
|
||||
|
@ -134,6 +141,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "sync"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Sync")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.sync()
|
||||
|
@ -142,6 +150,7 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
def handle_cast(:trigger, %{pin_binding: %{special_action: "take_photo"} = pin_binding} = state) do
|
||||
FarmbotCore.Logger.info(1, "#{pin_binding} triggered, executing Take Photo")
|
||||
|
||||
AST.Factory.new()
|
||||
|> AST.Factory.rpc_request("pin_binding.#{pin_binding.pin_num}")
|
||||
|> AST.Factory.take_photo()
|
||||
|
@ -182,10 +191,13 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.PinBinding do
|
|||
|
||||
defp execute(%AST{} = ast, state) do
|
||||
case FarmbotCeleryScript.execute(ast, make_ref()) do
|
||||
:ok -> :ok
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
{:error, reason} ->
|
||||
FarmbotCore.Logger.error 1, "error executing #{state.pin_binding}: #{reason}"
|
||||
FarmbotCore.Logger.error(1, "error executing #{state.pin_binding}: #{reason}")
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
|||
|
||||
@impl GenServer
|
||||
def init([regimen_instance, _args]) do
|
||||
Logger.warn "RegimenInstance #{inspect(regimen_instance)} initializing"
|
||||
|
||||
with %Regimen{} <- regimen_instance.regimen,
|
||||
%FarmEvent{} <- regimen_instance.farm_event do
|
||||
send self(), :schedule
|
||||
|
@ -40,25 +38,25 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
|||
def handle_info(:schedule, state) do
|
||||
regimen_instance = state.regimen_instance
|
||||
# load the sequence and calculate the scheduled_at time
|
||||
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
|
||||
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
|
||||
scheduled_at = DateTime.add(regimen_instance.epoch, offset, :millisecond)
|
||||
sequence = Asset.get_sequence(sequence_id) || raise("sequence #{sequence_id} is not synced")
|
||||
%{scheduled_at: scheduled_at, sequence: sequence}
|
||||
end)
|
||||
# get rid of any item that has already been scheduled/executed
|
||||
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
|
||||
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
|
||||
Asset.get_regimen_instance_execution(regimen_instance, scheduled_at)
|
||||
end)
|
||||
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
|
||||
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
|
||||
schedule_sequence(regimen_instance, sequence, at)
|
||||
end)
|
||||
{:noreply, state}
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({FarmbotCeleryScript, {:scheduled_execution, scheduled_at, executed_at, result}}, state) do
|
||||
status = case result do
|
||||
:ok -> "ok"
|
||||
{:error, reason} ->
|
||||
{:error, reason} ->
|
||||
FarmbotCore.Logger.error(2, "Regimen scheduled at #{scheduled_at} failed to execute: #{reason}")
|
||||
reason
|
||||
end
|
||||
|
@ -81,11 +79,11 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
|||
regimen_params = AST.decode(regimen_instance.regimen.body)
|
||||
# there may be many sequence scopes from here downward
|
||||
celery_ast = AST.decode(sequence)
|
||||
celery_args =
|
||||
celery_args =
|
||||
celery_ast.args
|
||||
|> Map.put(:sequence_name, sequence.name)
|
||||
|> Map.put(:locals, %{celery_ast.args.locals | body: celery_ast.args.locals.body ++ regimen_params ++ farm_event_params})
|
||||
|
||||
|
||||
celery_ast = %{celery_ast | args: celery_args}
|
||||
FarmbotCeleryScript.schedule(celery_ast, at, sequence)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
defmodule FarmbotCore.BotState do
|
||||
@moduledoc "Central State accumulator."
|
||||
alias FarmbotCore.BotStateNG
|
||||
alias FarmbotCore.BotState.JobProgress.Percent
|
||||
|
||||
require FarmbotCore.Logger
|
||||
use GenServer
|
||||
|
||||
|
@ -28,7 +30,7 @@ defmodule FarmbotCore.BotState do
|
|||
def set_position(bot_state_server \\ __MODULE__, x, y, z) do
|
||||
GenServer.call(bot_state_server, {:set_position, x, y, z})
|
||||
end
|
||||
|
||||
|
||||
@doc "Sets the location_data.load"
|
||||
def set_load(bot_state_server \\ __MODULE__, x, y, z) do
|
||||
GenServer.call(bot_state_server, {:set_load, x, y, z})
|
||||
|
@ -161,6 +163,10 @@ defmodule FarmbotCore.BotState do
|
|||
GenServer.call(bot_state_server, :enter_maintenance_mode)
|
||||
end
|
||||
|
||||
def job_in_progress?(job_name, bot_state_server \\ __MODULE__) do
|
||||
GenServer.call(bot_state_server, {:job_in_progress?, job_name})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
GenServer.start_link(__MODULE__, args, opts)
|
||||
|
@ -175,6 +181,13 @@ defmodule FarmbotCore.BotState do
|
|||
FarmbotCore.Logger.error 1, "BotState crashed! #{inspect(reason)}"
|
||||
end
|
||||
|
||||
def handle_call({:job_in_progress?, job_name}, _from, state) do
|
||||
progress = (state.tree.jobs[job_name] || %Percent{}).percent
|
||||
|
||||
in_progress? = (progress > 0.0 && progress < 100.0)
|
||||
{:reply, in_progress?, state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:subscribe, {pid, _} = _from, state) do
|
||||
# TODO Just replace this with Elixir.Registry?
|
||||
|
|
|
@ -6,11 +6,16 @@ defmodule FarmbotCore.BotState.Supervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
Supervisor.init(children(), [strategy: :one_for_all])
|
||||
end
|
||||
|
||||
def children do
|
||||
default = [
|
||||
FarmbotCore.BotState,
|
||||
FarmbotCore.BotState.FileSystem,
|
||||
FarmbotCore.BotState.SchedulerUsageReporter
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_all])
|
||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ defmodule FarmbotCore.BotStateNG.LocationData.Vec3 do
|
|||
|
||||
def new do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{x: -1, y: -1, z: -1})
|
||||
|> changeset(%{x: nil, y: nil, z: nil})
|
||||
|> apply_changes()
|
||||
end
|
||||
|
||||
|
@ -29,4 +29,4 @@ defmodule FarmbotCore.BotStateNG.LocationData.Vec3 do
|
|||
vec3
|
||||
|> cast(params, [:x, :y, :z])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -210,7 +210,7 @@ defmodule FarmbotCore.BotStateNG.McuParams do
|
|||
pin_guard_4_active_state: mcu_params.pin_guard_4_active_state,
|
||||
pin_guard_5_pin_nr: mcu_params.pin_guard_5_pin_nr,
|
||||
pin_guard_5_time_out: mcu_params.pin_guard_5_time_out,
|
||||
pin_guard_5_active_state: :pin_guard_5_active_stat
|
||||
pin_guard_5_active_state: mcu_params.pin_guard_5_active_state
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -7,9 +7,12 @@ defmodule FarmbotCore.Config.Supervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{FarmbotCore.Config.Repo, []},
|
||||
]
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
Supervisor.init(children(), strategy: :one_for_one)
|
||||
end
|
||||
|
||||
def children do
|
||||
default = [ {FarmbotCore.Config.Repo, []} ]
|
||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,10 +4,10 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
"""
|
||||
|
||||
alias FarmbotCeleryScript.AST
|
||||
alias FarmbotCore.FarmwareRuntime.PipeWorker
|
||||
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
|
||||
alias FarmbotCore.Asset.FarmwareInstallation.Manifest
|
||||
alias FarmbotCore.AssetWorker.FarmbotCore.Asset.FarmwareInstallation
|
||||
alias FarmbotCore.BotState.FileSystem
|
||||
alias FarmbotCore.FarmwareRuntime.PipeWorker
|
||||
alias FarmbotCore.Project
|
||||
import FarmwareInstallation, only: [install_dir: 1]
|
||||
|
||||
|
@ -67,7 +67,8 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
|
||||
@doc "Stop a farmware"
|
||||
def stop(pid) do
|
||||
Logger.info "Terminating farmware process"
|
||||
Logger.info("Terminating farmware process")
|
||||
|
||||
if Process.alive?(pid) do
|
||||
GenServer.stop(pid, :normal)
|
||||
end
|
||||
|
@ -75,7 +76,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
|
||||
def init([manifest, env, caller]) do
|
||||
package = manifest.package
|
||||
<<clause1 :: binary-size(8), _::binary>> = Ecto.UUID.generate()
|
||||
<<clause1::binary-size(8), _::binary>> = Ecto.UUID.generate()
|
||||
|
||||
request_pipe =
|
||||
Path.join([
|
||||
|
@ -109,7 +110,8 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
)
|
||||
|
||||
# Start the plugin.
|
||||
Logger.debug "spawning farmware: #{exec} #{manifest.args}"
|
||||
Logger.debug("spawning farmware: #{exec} #{manifest.args}")
|
||||
|
||||
{cmd, _} = spawn_monitor(MuonTrap, :cmd, ["sh", ["-c", "#{exec} #{manifest.args}"], opts])
|
||||
|
||||
state = %State{
|
||||
|
@ -125,7 +127,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
response_pipe_handle: resp
|
||||
}
|
||||
|
||||
send self(), :timeout
|
||||
send(self(), :timeout)
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
|
@ -142,24 +144,23 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
end
|
||||
|
||||
def handle_info(msg, %{context: :error} = state) do
|
||||
Logger.warn "unhandled message in error state: #{inspect(msg)}"
|
||||
Logger.warn("unhandled message in error state: #{inspect(msg)}")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, ref, {:error, reason}}, %{scheduler_ref: ref} = state) do
|
||||
send state.caller, {:error, reason}
|
||||
send(state.caller, {:error, reason})
|
||||
{:noreply, %{state | ref: nil, context: :error}}
|
||||
end
|
||||
|
||||
def handle_info({:step_complete, ref, :ok}, %{scheduler_ref: ref} = state) do
|
||||
label = UUID.uuid4()
|
||||
result = %AST{kind: :rpc_ok, args: %{label: label}, body: []}
|
||||
result = %AST{kind: :rpc_ok, args: %{label: state.rpc.args.label}, body: []}
|
||||
|
||||
ipc = add_header(result)
|
||||
_reply = PipeWorker.write(state.response_pipe_handle, ipc)
|
||||
# Make sure to `timeout` after this one to go back to the
|
||||
# get_header context. This will cause another rpc to be processed.
|
||||
send self(), :timeout
|
||||
send(self(), :timeout)
|
||||
{:noreply, %{state | rpc: nil, context: :get_header}}
|
||||
end
|
||||
|
||||
|
@ -174,14 +175,14 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
# didn't pick up the scheduled AST in a reasonable amount of time.
|
||||
def handle_info(:timeout, %{context: :process_request} = state) do
|
||||
Logger.error("Timeout waiting for #{inspect(state.rpc)} to be processed")
|
||||
send state.caller, {:error, :rpc_timeout}
|
||||
send(state.caller, {:error, :rpc_timeout})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
|
||||
# farmware exit
|
||||
def handle_info({:DOWN, _ref, :process, _pid, _reason}, %{cmd: _cmd_pid} = state) do
|
||||
Logger.debug("Farmware exit")
|
||||
send state.caller, {:error, :farmware_exit}
|
||||
send(state.caller, {:error, :farmware_exit})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
|
||||
|
@ -200,14 +201,14 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
# error result of an io:read/2 in :get_header context
|
||||
def handle_info({PipeWorker, _ref, {:ok, data}}, %{context: :get_header} = state) do
|
||||
Logger.error("Bad header: #{inspect(data, base: :hex, limit: :infinity)}")
|
||||
send state.caller, {:error, {:unhandled_packet, data}}
|
||||
send(state.caller, {:error, {:unhandled_packet, data}})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
|
||||
# error result of an io:read/2 in :get_header context
|
||||
def handle_info({PipeWorker, _ref, error}, %{context: :get_header} = state) do
|
||||
Logger.error("Bad header: #{inspect(error)}")
|
||||
send state.caller, {:error, :bad_packet_header}
|
||||
send(state.caller, {:error, :bad_packet_header})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
|
||||
|
@ -219,7 +220,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
# error result of an io:read/2 in :get_header context
|
||||
def handle_info({PipeWorker, _ref, error}, %{context: :get_payload} = state) do
|
||||
Logger.error("Bad payload: #{inspect(error)}")
|
||||
send state.caller, {:error, :bad_packet_payload}
|
||||
send(state.caller, {:error, :bad_packet_payload})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
|
||||
|
@ -247,10 +248,12 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
Logger.debug("executing rpc from farmware: #{inspect(rpc)}")
|
||||
# todo(connor) replace this with StepRunner?
|
||||
FarmbotCeleryScript.execute(rpc, ref)
|
||||
{:noreply, %{state | rpc: rpc, scheduler_ref: ref, context: :process_request}, @error_timeout_ms}
|
||||
|
||||
{:noreply, %{state | rpc: rpc, scheduler_ref: ref, context: :process_request},
|
||||
@error_timeout_ms}
|
||||
else
|
||||
{:error, reason} ->
|
||||
send state.caller, {:error, reason}
|
||||
send(state.caller, {:error, reason})
|
||||
{:noreply, %{state | context: :error}}
|
||||
end
|
||||
end
|
||||
|
@ -300,6 +303,7 @@ defmodule FarmbotCore.FarmwareRuntime do
|
|||
header =
|
||||
<<@packet_header_token::size(16)>> <>
|
||||
:binary.copy(<<0x00>>, 4) <> <<byte_size(payload)::big-size(32)>>
|
||||
|
||||
header <> payload
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,14 +9,7 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
require FarmbotCore.Logger
|
||||
alias FarmbotFirmware.{UARTTransport, StubTransport}
|
||||
alias FarmbotCore.{Asset, Config}
|
||||
@attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:attempt_threshold]
|
||||
@attempt_threshold || Mix.raise """
|
||||
Firmware open attempt threshold not configured:
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, [
|
||||
attempt_threshold: 10
|
||||
]
|
||||
"""
|
||||
@attempt_threshold Application.get_env(:farmbot_core, __MODULE__)[:attempt_threshold] || 5
|
||||
|
||||
@doc false
|
||||
def start_link(args, opts \\ [name: __MODULE__]) do
|
||||
|
@ -25,7 +18,10 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
|
||||
@doc false
|
||||
def swap_transport(tty) do
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware, transport: UARTTransport, device: tty)
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware,
|
||||
transport: UARTTransport,
|
||||
device: tty,
|
||||
reset: FarmbotCore.FirmwareResetter)
|
||||
# Swap transport on FW module.
|
||||
# Close tranpsort if it is open currently.
|
||||
_ = FarmbotFirmware.close_transport()
|
||||
|
@ -33,7 +29,9 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
end
|
||||
|
||||
def unswap_transport() do
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware, transport: StubTransport)
|
||||
Application.put_env(:farmbot_firmware, FarmbotFirmware,
|
||||
transport: StubTransport,
|
||||
reset: FarmbotCore.FirmwareResetter)
|
||||
# Swap transport on FW module.
|
||||
# Close tranpsort if it is open currently.
|
||||
_ = FarmbotFirmware.close_transport()
|
||||
|
@ -77,21 +75,21 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
|||
{:noreply, increment_attempts(%{state | timer: timer})}
|
||||
|
||||
firmware_hardware == "none" && needs_open? ->
|
||||
FarmbotCore.Logger.debug 3, "Firmware needs to be closed"
|
||||
FarmbotCore.Logger.debug 3, "Closing firmware..."
|
||||
unswap_transport()
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||
timer = Process.send_after(self(), :open, 5000)
|
||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||
|
||||
needs_open? ->
|
||||
FarmbotCore.Logger.debug 3, "Firmware needs to be opened"
|
||||
FarmbotCore.Logger.debug 3, "Opening firmware..."
|
||||
case swap_transport(firmware_path) do
|
||||
:ok ->
|
||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||
timer = Process.send_after(self(), :open, 5000)
|
||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||
_ ->
|
||||
FarmbotCore.Logger.debug 3, "Firmware failed to open"
|
||||
other ->
|
||||
FarmbotCore.Logger.debug 3, "Not ready to open yet, will retry in 5s (#{inspect(other)})"
|
||||
timer = Process.send_after(self(), :open, 5000)
|
||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||
end
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
defmodule FarmbotCore.FirmwareResetter do
|
||||
if Code.ensure_compiled?(Circuits.GPIO) do
|
||||
@gpio Circuits.GPIO
|
||||
else
|
||||
@gpio nil
|
||||
end
|
||||
alias FarmbotCore.Asset
|
||||
require FarmbotCore.Logger
|
||||
|
||||
def reset(package \\ nil) do
|
||||
pkg = package || Asset.fbos_config(:firmware_hardware)
|
||||
FarmbotCore.Logger.debug(3, "Attempting to retrieve #{pkg} reset function.")
|
||||
{:ok, fun} = find_reset_fun(pkg)
|
||||
fun.()
|
||||
end
|
||||
|
||||
def find_reset_fun("express_k10") do
|
||||
FarmbotCore.Logger.debug(3, "Using special express reset function")
|
||||
{:ok, fn -> express_reset_fun() end}
|
||||
end
|
||||
|
||||
def find_reset_fun(_) do
|
||||
FarmbotCore.Logger.debug(3, "Using default reset function")
|
||||
{:ok, fn -> :ok end}
|
||||
end
|
||||
|
||||
def express_reset_fun() do
|
||||
try do
|
||||
gpio_module = @gpio
|
||||
FarmbotCore.Logger.debug(3, "Begin MCU reset")
|
||||
{:ok, gpio} = gpio_module.open(19, :output)
|
||||
:ok = gpio_module.write(gpio, 0)
|
||||
:ok = gpio_module.write(gpio, 1)
|
||||
Process.sleep(1000)
|
||||
:ok = gpio_module.write(gpio, 0)
|
||||
FarmbotCore.Logger.debug(3, "Finish MCU Reset")
|
||||
:ok
|
||||
rescue
|
||||
ex ->
|
||||
message = Exception.message(ex)
|
||||
msg = "Express reset failed #{message}"
|
||||
FarmbotCore.Logger.error(3, msg)
|
||||
{:error, msg}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,7 +16,14 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_position_change([{_axis, _value}]) do
|
||||
def handle_position_change([{axis, 0.0}]) do
|
||||
FarmbotCore.Logger.warn(1, "#{axis}-axis stopped at home")
|
||||
:noop
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_position_change([{axis, _}]) do
|
||||
FarmbotCore.Logger.warn(1, "#{axis}-axis stopped at maximum")
|
||||
:noop
|
||||
end
|
||||
|
||||
|
@ -27,7 +34,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_axis_timeout(axis) do
|
||||
FarmbotCore.Logger.error 1, "Axis #{axis} timed out waiting for movement to complete"
|
||||
FarmbotCore.Logger.error(1, "#{axis}-axis timed out waiting for movement to complete")
|
||||
:noop
|
||||
end
|
||||
|
||||
|
@ -48,7 +55,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
|
||||
# this is a bug in the firmware code i think
|
||||
def handle_encoders_scaled([]), do: :noop
|
||||
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_encoders_raw(x: x, y: y, z: z) do
|
||||
:ok = BotState.set_encoders_raw(x, y, z)
|
||||
|
@ -64,6 +71,7 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
%{param => value}
|
||||
|> Asset.update_firmware_config!()
|
||||
|> Asset.Private.mark_dirty!(%{})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -75,31 +83,56 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_software_version([version]) do
|
||||
:ok = BotState.set_firmware_version(version)
|
||||
|
||||
case String.split(version, ".") do
|
||||
# Ramps
|
||||
[_, _, _, "R"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("arduino")
|
||||
[_, _, _, "R", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("arduino")
|
||||
|
||||
# Farmduino
|
||||
[_, _, _, "F"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino")
|
||||
[_, _, _, "F", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino")
|
||||
|
||||
# Farmduino V14
|
||||
[_, _, _, "G"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k14")
|
||||
[_, _, _, "G", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k14")
|
||||
|
||||
# Farmduino V15
|
||||
[_, _, _, "H"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k15")
|
||||
[_, _, _, "H", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("farmduino_k15")
|
||||
|
||||
# Express V10
|
||||
[_, _, _, "E"] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("express_k10")
|
||||
[_, _, _, "E", _] ->
|
||||
_ = Leds.red(:solid)
|
||||
:ok = BotState.set_firmware_hardware("express_k10")
|
||||
|
||||
[_, _, _, "S"] ->
|
||||
_ = Leds.red(:slow_blink)
|
||||
:ok = BotState.set_firmware_version("none")
|
||||
:ok = BotState.set_firmware_hardware("none")
|
||||
[_, _, _, "S", _] ->
|
||||
_ = Leds.red(:slow_blink)
|
||||
:ok = BotState.set_firmware_version("none")
|
||||
:ok = BotState.set_firmware_hardware("none")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -159,8 +192,15 @@ defmodule FarmbotCore.FirmwareSideEffects do
|
|||
@impl FarmbotFirmware.SideEffects
|
||||
def handle_debug_message([message]) do
|
||||
fbos_config = Asset.fbos_config()
|
||||
should_log? = fbos_config.firmware_debug_log || fbos_config.arduino_debug_messages
|
||||
should_log? && FarmbotCore.Logger.debug(3, "Firmware debug message: " <> message)
|
||||
should_log? = fbos_config.firmware_debug_log || fbos_config.arduino_debug_messages
|
||||
should_log? && do_send_debug_message(message)
|
||||
end
|
||||
|
||||
# TODO(Rick): 0 means OK, but firmware debug logs say "error 0". Why?
|
||||
def do_send_debug_message("error 0"), do: do_send_debug_message("OK")
|
||||
|
||||
def do_send_debug_message(message) do
|
||||
FarmbotCore.Logger.debug(3, "Firmware debug message: " <> message)
|
||||
end
|
||||
|
||||
@impl FarmbotFirmware.SideEffects
|
||||
|
|
|
@ -3,17 +3,14 @@ defmodule FarmbotCore.FirmwareTTYDetector do
|
|||
require Logger
|
||||
alias Circuits.UART
|
||||
|
||||
@expected_names Application.get_env(:farmbot_core, __MODULE__)[:expected_names]
|
||||
@expected_names ||
|
||||
Mix.raise("""
|
||||
Please configure `expected_names` for TTYDetector.
|
||||
|
||||
config :farmbot_core, FarmbotCore.FirmwareTTYDetector,
|
||||
expected_names: ["ttyS0", "ttyNotReal"]
|
||||
""")
|
||||
|
||||
@error_retry_ms 5_000
|
||||
|
||||
if System.get_env("FARMBOT_TTY") do
|
||||
@expected_names ["ttyUSB0", "ttyAMA0", "ttyACM0", System.get_env("FARMBOT_TTY")]
|
||||
else
|
||||
@expected_names ["ttyUSB0", "ttyAMA0", "ttyACM0"]
|
||||
end
|
||||
|
||||
@doc "Gets the detected TTY"
|
||||
def tty(server \\ __MODULE__) do
|
||||
GenServer.call(server, :tty)
|
||||
|
@ -53,7 +50,6 @@ defmodule FarmbotCore.FirmwareTTYDetector do
|
|||
if farmbot_tty?(name) do
|
||||
{:noreply, name}
|
||||
else
|
||||
# Logger.warn("#{name} is not an expected Farmbot Firmware TTY")
|
||||
{:noreply, state, {:continue, rest}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
defmodule FarmbotCore.Leds do
|
||||
@moduledoc "API for controling Farmbot LEDS."
|
||||
@led_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
||||
@led_handler || Mix.raise("You forgot a led handler!")
|
||||
|
||||
@valid_status [:off, :solid, :slow_blink, :fast_blink, :really_fast_blink]
|
||||
|
||||
|
@ -15,29 +13,7 @@ defmodule FarmbotCore.Leds do
|
|||
def white4(status) when status in @valid_status, do: led_handler().white4(status)
|
||||
def white5(status) when status in @valid_status, do: led_handler().white5(status)
|
||||
|
||||
def factory_test(status) do
|
||||
red(:off)
|
||||
blue(:off)
|
||||
green(:off)
|
||||
yellow(:off)
|
||||
white1(:off)
|
||||
white2(:off)
|
||||
white3(:off)
|
||||
white4(:off)
|
||||
white5(:off)
|
||||
|
||||
red(status)
|
||||
blue(status)
|
||||
green(status)
|
||||
yellow(status)
|
||||
white1(status)
|
||||
white2(status)
|
||||
white3(status)
|
||||
white4(status)
|
||||
white5(status)
|
||||
end
|
||||
|
||||
defp led_handler,
|
||||
def led_handler,
|
||||
do: Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
||||
|
||||
def child_spec(opts) do
|
||||
|
|
|
@ -13,7 +13,8 @@ defmodule FarmbotCore.Leds.StubHandler do
|
|||
def white5(status), do: do_debug(:white, status)
|
||||
|
||||
defp do_debug(color, status) do
|
||||
msg = [IO.ANSI.reset(), "LED STATUS: ",
|
||||
unless System.get_env("LOG_SILENCE") do
|
||||
msg = [IO.ANSI.reset(), "LED STATUS: ",
|
||||
apply(IO.ANSI, color, []),
|
||||
status_in(status),
|
||||
to_string(color),
|
||||
|
@ -22,7 +23,8 @@ defmodule FarmbotCore.Leds.StubHandler do
|
|||
status_out(status),
|
||||
IO.ANSI.reset()
|
||||
]
|
||||
IO.puts(msg)
|
||||
IO.puts(msg)
|
||||
end
|
||||
end
|
||||
|
||||
defp status_in(:slow_blink), do: IO.ANSI.blink_slow()
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
# This module could have existed within FarmbotCore.Logger.
|
||||
# Pulling this function into a different module facilitates
|
||||
# mocking of tests.
|
||||
defmodule FarmbotCore.LogExecutor do
|
||||
alias FarmbotCore.Log
|
||||
|
||||
def execute(%Log{} = log) do
|
||||
logger_meta = [
|
||||
application: :farmbot,
|
||||
function: log.function,
|
||||
file: log.file,
|
||||
line: log.line,
|
||||
module: log.module,
|
||||
channels: log.meta[:channels] || log.meta["channels"],
|
||||
verbosity: log.verbosity,
|
||||
assertion_passed: log.meta[:assertion_passed]
|
||||
]
|
||||
|
||||
level = log.level
|
||||
|
||||
logger_level =
|
||||
if level in [:info, :debug, :warn, :error],
|
||||
do: level,
|
||||
else: :info
|
||||
|
||||
unless System.get_env("LOG_SILENCE") do
|
||||
Elixir.Logger.bare_log(logger_level, log, logger_meta)
|
||||
end
|
||||
log
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
alias FarmbotCore.{Log, Logger.Repo}
|
||||
import Ecto.Query
|
||||
@log_types [:info, :debug, :busy, :warn, :success, :error, :fun, :assertion]
|
||||
|
||||
@doc "Send a debug message to log endpoints"
|
||||
defmacro debug(verbosity, message, meta \\ []) do
|
||||
|
@ -57,23 +58,27 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
def insert_log!(params) do
|
||||
changeset = Log.changeset(%Log{}, params)
|
||||
|
||||
try do
|
||||
hash = Ecto.Changeset.get_field(changeset, :hash)
|
||||
|
||||
case Repo.get_by(Log, hash: hash) do
|
||||
nil ->
|
||||
nil ->
|
||||
Repo.insert!(changeset)
|
||||
old ->
|
||||
params =
|
||||
|
||||
old ->
|
||||
params =
|
||||
params
|
||||
|> Map.put(:inserted_at, DateTime.utc_now)
|
||||
|> Map.put(:inserted_at, DateTime.utc_now())
|
||||
|> Map.put(:duplicates, old.duplicates + 1)
|
||||
old
|
||||
|> Log.changeset(params)
|
||||
|> Repo.update!()
|
||||
|
||||
old
|
||||
|> Log.changeset(params)
|
||||
|> Repo.update!()
|
||||
end
|
||||
catch
|
||||
kind, err ->
|
||||
IO.warn("Error inserting log: #{kind} #{inspect(err)}", __STACKTRACE__)
|
||||
IO.warn("Error inserting log: #{kind} #{inspect(err)}", __STACKTRACE__)
|
||||
Ecto.Changeset.apply_changes(changeset)
|
||||
end
|
||||
end
|
||||
|
@ -94,13 +99,16 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
@doc false
|
||||
def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta)
|
||||
when level in [:info, :debug, :busy, :warn, :success, :error, :fun, :assertion] and is_number(verbosity) and
|
||||
is_binary(message) and is_list(meta) do
|
||||
when level in @log_types and
|
||||
is_number(verbosity) and
|
||||
is_binary(message) and
|
||||
is_list(meta) do
|
||||
fun =
|
||||
case env.function do
|
||||
{fun, ar} -> "#{fun}/#{ar}"
|
||||
nil -> "no_function"
|
||||
end
|
||||
|
||||
%{
|
||||
level: level,
|
||||
verbosity: verbosity,
|
||||
|
@ -116,29 +124,8 @@ defmodule FarmbotCore.Logger do
|
|||
|
||||
@doc false
|
||||
def dispatch_log(params) do
|
||||
params
|
||||
|> insert_log!()
|
||||
|> elixir_log()
|
||||
end
|
||||
|
||||
defp elixir_log(%Log{} = log) do
|
||||
logger_meta = [
|
||||
application: :farmbot,
|
||||
function: log.function,
|
||||
file: log.file,
|
||||
line: log.line,
|
||||
module: log.module,
|
||||
channels: log.meta[:channels] || log.meta["channels"],
|
||||
verbosity: log.verbosity,
|
||||
assertion_passed: log.meta[:assertion_passed]
|
||||
# TODO Connor - fix time
|
||||
# time: time
|
||||
]
|
||||
|
||||
level = log.level
|
||||
logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info
|
||||
Elixir.Logger.bare_log(logger_level, log, logger_meta)
|
||||
log
|
||||
log = insert_log!(params)
|
||||
FarmbotCore.LogExecutor.execute(log)
|
||||
end
|
||||
|
||||
@doc "Helper function for deciding if a message should be logged or not."
|
||||
|
|
|
@ -7,11 +7,13 @@ defmodule FarmbotCore.Logger.Supervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
supervisor(FarmbotCore.Logger.Repo, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_all]
|
||||
supervise(children, opts)
|
||||
supervise(children(), opts)
|
||||
end
|
||||
|
||||
def children do
|
||||
default = [supervisor(FarmbotCore.Logger.Repo, [])]
|
||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,11 +10,16 @@ defmodule FarmbotCore.StorageSupervisor do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
Supervisor.init(children(), [strategy: :one_for_one])
|
||||
end
|
||||
|
||||
def children do
|
||||
default = [
|
||||
FarmbotCore.Logger.Supervisor,
|
||||
FarmbotCore.Config.Supervisor,
|
||||
FarmbotCore.Asset.Supervisor
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||
Keyword.get(config, :children, default)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,21 +1,6 @@
|
|||
defmodule FarmbotCore.TimeUtils do
|
||||
@moduledoc "Helper functions for working with time."
|
||||
|
||||
def format_time(%DateTime{} = dt) do
|
||||
"#{format_num(dt.month)}/#{format_num(dt.day)}/#{dt.year} " <>
|
||||
"at #{format_num(dt.hour)}:#{format_num(dt.minute)}"
|
||||
end
|
||||
|
||||
defp format_num(num), do: :io_lib.format('~2..0B', [num]) |> to_string
|
||||
|
||||
# returns midnight of today
|
||||
@spec build_epoch(DateTime.t) :: DateTime.t
|
||||
def build_epoch(time) do
|
||||
tz = FarmbotCore.Asset.fbos_config().timezone
|
||||
n = Timex.Timezone.convert(time, tz)
|
||||
Timex.shift(n, hours: -n.hour, seconds: -n.second, minutes: -n.minute)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Compares a datetime with another.
|
||||
• -1 -- the first date comes before the second one
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
defmodule FarmbotCore.MixProject do
|
||||
use Mix.Project
|
||||
@target System.get_env("MIX_TARGET") || "host"
|
||||
@version Path.join([__DIR__, "..", "VERSION"]) |> File.read!() |> String.trim()
|
||||
@branch System.cmd("git", ~w"rev-parse --abbrev-ref HEAD") |> elem(0) |> String.trim()
|
||||
@elixir_version Path.join([__DIR__, "..", "ELIXIR_VERSION"]) |> File.read!() |> String.trim()
|
||||
@version Path.join([__DIR__, "..", "VERSION"])
|
||||
|> File.read!()
|
||||
|> String.trim()
|
||||
@branch System.cmd("git", ~w"rev-parse --abbrev-ref HEAD")
|
||||
|> elem(0)
|
||||
|> String.trim()
|
||||
@elixir_version Path.join([__DIR__, "..", "ELIXIR_VERSION"])
|
||||
|> File.read!()
|
||||
|> String.trim()
|
||||
|
||||
defp commit do
|
||||
System.cmd("git", ~w"rev-parse --verify HEAD") |> elem(0) |> String.trim()
|
||||
|
@ -59,7 +65,8 @@ defmodule FarmbotCore.MixProject do
|
|||
# Run "mix help deps" to learn about dependencies.
|
||||
defp deps do
|
||||
[
|
||||
{:farmbot_celery_script, path: "../farmbot_celery_script", env: Mix.env()},
|
||||
{:farmbot_celery_script,
|
||||
path: "../farmbot_celery_script", env: Mix.env()},
|
||||
{:farmbot_firmware, path: "../farmbot_firmware", env: Mix.env()},
|
||||
{:farmbot_telemetry, path: "../farmbot_telemetry", env: Mix.env()},
|
||||
{:elixir_make, "~> 0.6", runtime: false},
|
||||
|
@ -68,7 +75,9 @@ defmodule FarmbotCore.MixProject do
|
|||
{:jason, "~> 1.1"},
|
||||
{:muontrap, "~> 0.5"},
|
||||
{:excoveralls, "~> 0.10", only: [:test], targets: [:host]},
|
||||
{:dialyxir, "~> 1.0.0-rc.3", only: [:dev], targets: [:host], runtime: false},
|
||||
{:mimic, "~> 1.1", only: [:test]},
|
||||
{:dialyxir, "~> 1.0.0-rc.3",
|
||||
only: [:dev], targets: [:host], runtime: false},
|
||||
{:ex_doc, "~> 0.21.2", only: [:dev], targets: [:host], runtime: false}
|
||||
]
|
||||
end
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
|
||||
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
|
||||
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"},
|
||||
"muontrap": {:hex, :muontrap, "0.5.0", "0b885a4095e990000d519441bccb8f037a9c4c35908720e7814a516a606be278", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"nerves_uart": {:hex, :nerves_uart, "1.2.0", "195424116b925cd3bf9d666be036c2a80655e6ca0f8d447e277667a60005c50e", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
|
||||
|
|
|
@ -8,13 +8,29 @@ defmodule FarmbotCore.Asset.Repo.Migrations.CreateRegimenInstancesTable do
|
|||
add(:epoch, :utc_datetime)
|
||||
add(:next, :utc_datetime)
|
||||
add(:next_sequence_id, :id)
|
||||
add(:regimen_id, references("regimens", type: :binary_id, column: :local_id))
|
||||
add(:farm_event_id, references("farm_events", type: :binary_id, column: :local_id))
|
||||
|
||||
add(
|
||||
:regimen_id,
|
||||
references("regimens", type: :binary_id, column: :local_id)
|
||||
)
|
||||
|
||||
add(
|
||||
:farm_event_id,
|
||||
references("farm_events", type: :binary_id, column: :local_id)
|
||||
)
|
||||
|
||||
add(:monitor, :boolean, default: true)
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
||||
create(unique_index("persistent_regimens", [:local_id, :regimen_id, :farm_event_id]))
|
||||
create(
|
||||
unique_index("persistent_regimens", [
|
||||
:local_id,
|
||||
:regimen_id,
|
||||
:farm_event_id
|
||||
])
|
||||
)
|
||||
|
||||
create(unique_index("persistent_regimens", :started_at))
|
||||
create(unique_index("persistent_regimens", :epoch))
|
||||
end
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.ForceResyncPoints do
|
||||
use Ecto.Migration
|
||||
|
||||
alias FarmbotCore.Asset.{Repo, Point}
|
||||
import Ecto.Query, only: [from: 2]
|
||||
alias FarmbotCore.Asset.Repo
|
||||
|
||||
def change do
|
||||
for %{id: id} = point when is_integer(id) <- Repo.all(Point) do
|
||||
Repo.delete!(point)
|
||||
end
|
||||
Repo.query("TRUNCATE points")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.ForceResyncDevice do
|
||||
use Ecto.Migration
|
||||
alias FarmbotCore.Asset.{Repo, Device}
|
||||
|
||||
def change do
|
||||
execute("UPDATE devices SET updated_at = \"1970-11-07 16:52:31.618000\"")
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
defmodule FarmbotCore.Asset.Repo.Migrations.ForceResyncDeviceForMountedToolId do
|
||||
use Ecto.Migration
|
||||
alias FarmbotCore.Asset.{Repo, Device}
|
||||
|
||||
def change do
|
||||
execute("UPDATE devices SET updated_at = \"1970-11-07 16:52:31.618000\"")
|
||||
|
|
|
@ -9,6 +9,8 @@ defmodule FarmbotCore.Asset.Repo.Migrations.ResyncFirmwareConfig do
|
|||
end
|
||||
|
||||
# will resync the firmware params
|
||||
execute("UPDATE firmware_configs SET updated_at = \"1970-11-07 16:52:31.618000\"")
|
||||
execute(
|
||||
"UPDATE firmware_configs SET updated_at = \"1970-11-07 16:52:31.618000\""
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue